home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Developer Essentials / DTS Sample Code / Macintosh Sample Code / SC.023.FracApp 2.0 / UFracApp.inc1.p < prev    next >
Encoding:
Text File  |  1990-04-30  |  152.2 KB  |  4,252 lines  |  [TEXT/MPS ]

  1. {[j=20/53/1$]}
  2.  
  3. {-------------------------------------------------------------------------------------------}
  4. { All of these constants and globals are used only in this unit, and so are defined in the
  5.   IMPLEMENTATION section of our show. This is because if we kept them in the INTERFACE
  6.   section and we changed them, MFracApp and UAreaSelector would get recompiled when they
  7.   didn’t need to. Putting them here prevents that from happening, because MFracApp and
  8.   UAreaSelector are only dependent on the INTERFACE section, not the IMPLEMENTATION. }
  9. {-------------------------------------------------------------------------------------------}
  10.  
  11. CONST
  12.  
  13.  
  14.     { Constants used for identification }
  15.  
  16.     kNormalVersion        = 4;                        { Identifies a document created by
  17.                                                      FracApp 2.0 and using the
  18.                                                      TNormalFracAppEngine }
  19.     kFastVersion        = 5;                        { Identifies a document created by
  20.                                                      FracApp 2.0 and using the TFastFracApp
  21.                                                      engine }
  22.     kUnknownVersion     = - 1;                        { Used when initializing the version
  23.                                                      number before the real version is
  24.                                                      known. }
  25.  
  26. { We define two different palettes for two different situations. We’d like to be able to
  27.   animate when we can; in that case, we attach the palette identified with
  28.   kAnimatingPaletteID to each window we make. If we can’t animate our colors (which occurs
  29.   when we don’t have 32-bit Color QuickDraw), then we fall back on a palette that at least
  30.   guarantees we have the colors we need (kTolerantPalette). }
  31.  
  32.     kTolerantPalette    = 1001;                     { palette ID for not animating }
  33.     kAnimatingPalette    = 1002;                     { palette ID for animating }
  34.     kClut                = 501;                        { ID for the clut resource that we use
  35.                                                      for offscreen blinging. }
  36.  
  37. { Window and view resource numbers }
  38.  
  39.     kFracAppWindowID    = kDefaultWindowID;         { ID of the ‘view’ resource used to
  40.                                                      create our windows }
  41.     kAbout                = 4000;                     { ID for our about dialog }
  42.     kAbout2             = 4001;                     { Ditto }
  43.     kMultiDialog        = 2002;                     { Dialog Id for multipage dialog, gets
  44.                                                      number of pages to do.}
  45.     kHorItem            = 3;                        { item in dialog for EditText of
  46.                                                      horizontal page count. }
  47.     kVerItem            = 4;                        { item in dialog for EditText of vertical
  48.                                                      page count. }
  49.  
  50. { Constance for our own menu items. }
  51.  
  52.     cNewFromSelection    = 1000;                     { New Fractal from selection. }
  53.     cNewMultiPage        = 1001;                     { New Multipage… }
  54.     cAnimate            = 1002;                     { Start the palettes moving }
  55.     cJumble             = 1003;                     { Mix up the palette }
  56.     cNormal             = 1004;                     { Make the palette normal again }
  57.     cUse32CQD            = 1005;                     { Use 32CQD for offscreen management. }
  58.     cUseHomebrew        = 1006;                     { Use old method for offscreen stuff. }
  59.     cMake72dpi            = 1007;                     { Generate at 72 dpi for the screen. }
  60.     cMake300dpi         = 1008;                     { Generate a 300 dpi image for the
  61.                                                      printer }
  62.     cSlowAlgorithm        = 1009;                     { Use left/right, up/down method }
  63.     cFastAlgorithm        = 1010;                     { Use divide & conquer method }
  64.     cFirstWindowBase    = 2000;                     { Dummy item to find where windows go }
  65.     cContinuingMultiPage = 2001;                    { Passed to OpenNew when making add’l
  66.                                                      docs }
  67.  
  68. { The dimensions of our Fractal documents. These are tailored to the size of the spiffy
  69.   color printer we have in DTS. Your values may vary. }
  70.  
  71.     kRectRight            = 608;
  72.     kRectBottom         = 798;
  73.  
  74. { TFracAppDocument.LockThePixels takes a Boolean value that determines if we are actually
  75.   locking the pixels, or really unlocking them. These constants define what action to take. }
  76.  
  77.     kLock                = TRUE;
  78.     kUnlock             = NOT kLock;
  79.  
  80. { When setting the strings in the infobar (elapsed time, etc.) we need to differentiate
  81.   between setting those strings for the FIRST time, and all subsequent times. When calling
  82.   them for the first time, we pass kForceUpdate. All other times, we pass kDontForceUpdate. }
  83.  
  84.     kForceUpdate        = TRUE;
  85.     kDontForceUpdate    = NOT kForceUpdate;
  86.  
  87. { When calling SetPalette, we need to pass a Boolean that tells the Palette manager whether
  88.   or not we want updates when our window is activated. These two constants help clarify what
  89.   we are passing to SetPalette. }
  90.  
  91.     kWantUpdates        = TRUE;
  92.     kDontWantUpdates    = FALSE;
  93.  
  94. { String list (STR#) resource number and string indexes. These are used when building up the
  95.   string that describes the algorithm being used. That string is of the form “fractal
  96.   algorithm”/“offscreen routines being used”. }
  97.  
  98.     kAlgorithmStrings    = 1200;
  99.     kUsing                = 1;
  100.     kUnknownAlgorithm    = 2;
  101.     kNormalAlgorithm    = 3;
  102.     kFastAlgorithm        = 4;
  103.     kSlash                = 5;
  104.     kHomebrewRoutines    = 6;
  105.     k32CQDRoutines        = 7;
  106.  
  107. { Amount of time to spend on calculating fractals when we are in the foreground and in the
  108.   background. If we can use the Time Manager, then we specify these constants in terms of
  109.   milliseconds, and define a value that is used to convert our units to seconds. If we
  110.   cannot use the Time Manager, then we express our values in ticks, and set our conversion
  111.   value  appropriatedly. In either case, we set the background time to as small as possible. }
  112.  
  113. {$IFC NOT qPerform}
  114.     kFgCalcTime         = 50;                        { 50 msec = 50/1000 = 1/20 of sec }
  115.     kBkCalcTime         = 1;
  116.     kUnitsPerSecond     = 1000;
  117. {$ELSEC}
  118.     kFgCalcTime         = 3;                        { 3 ticks = 3/60 = 1/20 of sec }
  119.     kBkCalcTime         = 1;
  120.     kUnitsPerSecond     = 60;
  121. {$ENDC}
  122.  
  123. { Other Thangs... }
  124.  
  125.     kPICTHeaderSize     = 512;                        { 512 bytes off the file are used for our
  126.                                                      info and print info. }
  127.     kNumColors            = 195;                        { number of colors we animate, and use in
  128.                                                      calculation. }
  129.     kNo32BCQD            = - 25002;                    { error code if 32bCQD is not around.
  130.                                                      Used to display a warning that some
  131.                                                      menu items will be disabled. }
  132.     QD32Trap            = $AB03;                    { Trap number of 32 bit QuickDraw (not in
  133.                                                      MPW headers) }
  134.     kAnimationDelay     = 2;                        { Ticks between palette animation }
  135.  
  136.     kMinCCRectSize        = 4;                        { minimum CalcCity rect size }
  137.  
  138.     kWantSeconds        = TRUE;                        { passed to IUTimePString to indicate that
  139.                                                 we want the time string to include seconds. }
  140.  
  141. {-------------------------------------------------------------------------------------------}
  142. {$IFC NOT qPerform}
  143.  
  144. TYPE
  145.  
  146. { This is the record type we use when installing a time manager task. It includes a field
  147.   for an A5 so that our task can get to our global variables. In addition to a normal time
  148.   manager task record, we define two other fields that are used by the extended “no drift”
  149.   time manager. }
  150.  
  151.     MyTimeTask            = RECORD
  152.         A5:                 LONGINT;
  153.         taskRecord:         TMTask;
  154.         tmWakeUp:            LONGINT;                { In case the extended time manager is
  155.                                                      around }
  156.         tmReserved:         LONGINT;
  157.         END;
  158. {$ENDC}
  159.  
  160. {-------------------------------------------------------------------------------------------}
  161.  
  162. VAR
  163.  
  164. { This is a handle to the color table that we use throughout the program. It is loaded in
  165.   from a ‘clut’ resource early on in the program. It is used when we create an offscreen
  166.   world for the offscreen gDevice’s colortable, and also for some of the whizzy animation
  167.   effects that we do (see MakeOffWorlder, JumblePalette and RestorePalette) }
  168.  
  169.     gOurColors:         CTabHandle;
  170.  
  171. { We have two palettes to attach to windows, depending on whether or not we have 32-Bit
  172.   Color QuickDraw. We figure out which one to use when our application initializes itself,
  173.   and store its resource ID here. Then, whenever we need a palette, we just use the value
  174.   here, rather than refiguring the whole thing out every time. }
  175.  
  176.     gPaletteIDToUse:    Integer;
  177.  
  178. { When using system 6.0.2 or later, we can associate a palette with more than one window. If
  179.   we are running under those conditions, we get the palette we need just once, save it in
  180.   this variable, and attach it to any new windows we create. Otherwise, we just do a
  181.   GetNewPalette every time, and this variable goes unused. }
  182.  
  183.     gPalette:            PaletteHandle;
  184.  
  185. { Just because we HAVE 32-Bit CQD doesn’t mean that we want to use it. This variable is used
  186.   to control whether or not we want to use it if it’s available. It’s initially set to
  187.   gConfiguration.has32BitCQD. }
  188.  
  189.     gUse32BitCQD:        Boolean;
  190.  
  191. { Set to TRUE if we have selected the menu item for the Mariani/Silver algorithm. }
  192.  
  193.     gUseFastAlgorithm:    Boolean;
  194.  
  195. { Normally set to 72. It’s here so that we can implement other resolutions (like 300 dpi) in
  196.   the future. }
  197.  
  198.     gPrintResolution:    Integer;
  199.  
  200. { We set this global variable to TRUE if we want to do palette animation at idle time. We
  201.   also check it when we set up the menus so we know if we need to check the menu item or
  202.   not. }
  203.  
  204.     gAnimate:            Boolean;
  205.  
  206. { This Boolean is set to true when we have jumbled up the palettes. We use it when we are
  207.   setting up the menus so we know if we need to check the menu item or not. }
  208.  
  209.     gPaletteIsJumbled:    Boolean;
  210.  
  211. { We need some sort of clutch when animating. I initially animated whenever my DoIdle method
  212.   was called. However, on faster machines, we could get called more times in a second than
  213.   we care for. So we keep track of when we last animated, and make sure that a decent number
  214.   of ticks go by before animating again. This makes sure that our effects don’t go whizzing
  215.   by too fast on that 100MHz Mac III. }
  216.  
  217.     gLastAnimated:        LONGINT;
  218.  
  219. { We’d like to take advantage to default application palettes if the system supports them.
  220.   (see technote #211). To condition our program in the places necessary to support default
  221.   application palettes, we set this global variable when running on the those systems. }
  222.  
  223.     gHasDefaultApplicationPalettes: Boolean;
  224.  
  225. { When we are creating a new document, we have some preset values that define the number
  226.   range applied to the Mandelbrot calculations. On the other hand, if we are creating a new
  227.   document based on the selection, we need to initialize the document with that information.
  228.   The following boolean says which method of initialization we are using, and the PageRecord
  229.   holds the necessary information for when gCreateFromPageRecord is TRUE. These are accessed
  230.   in TFracAppDocument.DoInitialState. }
  231.  
  232.     gPageRecord:        PageRecord;
  233.  
  234. { For determining how long it takes to calculate a document, we use the Time Manager. But we
  235.   can only use the Time Manager if we are not using the performance tools, which use the
  236.   VIAs directly and interfere with the Time Manager. Everything would be fine if the the
  237.   performance tools used the Time Manager, but they don’t. At least, not as of MPW 3.2.
  238.   Therefore, we can use the Time Manager only if we don’t use the performance tools, so we
  239.   define this Time Manager task record only under that circumstance. }
  240.  
  241. {$IFC NOT qPerform}
  242.     gTMTask:            MyTimeTask;
  243. {$ENDC}
  244.  
  245. { FracApp makes large windows, which tend to hide each other. We have a Windows menu that
  246.   allows easy access to all windows. The way it works is this: all documents are shown in a
  247.   TFracAppWindow. This window’s Show method is overridden to call
  248.   TFracAppApplication.InstallWindowMenuItem when it is shown or hidden, and hence needs to
  249.   be added to or deleted from the Windows menu. InstallWindowMenuItem does what it needs in
  250.   order to remember what windows are visible, and then sets gRebuildWindowsMenu to TRUE.
  251.   Also, when a document’s name changes (say, we just saved it to disk), we need to update
  252.   its name in the menubar, so we again set gRebuildWindowsMenu to TRUE. The next time our
  253.   application’s DoSetupMenus is called, it calls BuildWindowsMenu. BuildWindowsMenu checks
  254.   this flag to see if it needs to do anything. If so, it rebuilds the menu, and clears the
  255.   flag. }
  256.  
  257.     gRebuildWindowsMenu: Boolean;
  258.  
  259. { The following variables describe the location of the dynamic parts of our Windows menu.
  260.   They are determined by looking for the dummy menu item with the command number
  261.   cFirstWindowBase. That menu item is removed, and any window names replace it. }
  262.  
  263.     gFirstWindowBase:    Integer;
  264.     gWindowMenuNumber:    Integer;
  265.  
  266. { We use the International Utilities for creating a time string when we are showing the
  267.   elapsed times. But we can’t just take the standard default format that the IU give us. We
  268.   have to munge them so that we don’t get things like AM or PM after the time, and so that
  269.   we get leading zeros in the places we want them. This handle holds a copy of the default
  270.   INTL(0) or itl0 resource, modified to our liking. }
  271.  
  272.     gIntlHandle:        Handle;
  273.  
  274. { So that we can intelligently move the items in our infobar around, we need to know how
  275.   wide the items are. Finding the width of the labels can be done by doing a StringWidth on
  276.   the text for those labels. In order to find the length of the time items, we create a
  277.   prototype string at application initialization time. This string is created by calling
  278.   IUTimeString with a time of zero. This will represent the width of a time string, as all
  279.   numerical digits are guaranteed to be the same width in all fonts. By creating this
  280.   string, we can use it for determining the spacing for the time elements. }
  281.  
  282.     gMaxWidthTimeString: Str255;
  283.  
  284. { The next globals are used for the QuickDraw bottlenecks when reading or writing a picture
  285.   to disk. These are needed, since the bottlenecks cannot be owned procedures, and we can’t
  286.   just use local variables. }
  287.  
  288.     gPictSize:            LONGINT;                    { number of bytes used for saving a PICT.
  289.                                                      }
  290.     gPictError:         OSErr;                        { do some error handling in bottleneck. }
  291.     gPictRefNum:        Integer;                    { Need the refnum of the open file too. }
  292.     gPictHandle:        PicHandle;                    { for reading/writing a picture. }
  293.  
  294. {-------------------------------------------------------------------------------------------}
  295.  
  296. PROCEDURE TimeCounterThatFetchesItsOwnTaskPtr;
  297.     EXTERNAL;
  298.  
  299. PROCEDURE TimeCounter;
  300.     EXTERNAL;
  301.  
  302. PROCEDURE InitCounter(A5: Ptr);
  303.     EXTERNAL;
  304.  
  305. PROCEDURE InsTimeNoDrift(tmTaskPtr: QElemPtr);
  306.     EXTERNAL;
  307.  
  308. {-------------------------------------------------------------------------------------------}
  309. {-----------------------------------Global Procedures---------------------------------------}
  310. {-------------------------------------------------------------------------------------------}
  311. {$S ARes}
  312.  
  313. FUNCTION EqualRGB(RGB1, RGB2: RGBColor): Boolean;
  314.  
  315. { Compare two RGB records. Return TRUE if their R, G, & B components are the same. }
  316.  
  317.     BEGIN
  318.         WITH RGB1 DO
  319.             EqualRGB := (red = RGB2.red) & (green = RGB2.green) & (blue = RGB2.blue);
  320.     END;
  321.  
  322. {-------------------------------------------------------------------------------------------}
  323. {$S AOpen}
  324.  
  325. PROCEDURE MySetPalette(theWindow: TWindow);
  326.  
  327. { Called whenever we create a window. This routine is an intelligent version of SetPalette.
  328.   It determines if we are running on a system that allows default application palettes or
  329.   not. If not, it gets a new palette, and attaches it to the window. Otherwise, it does
  330.   nothing, allowing the system to attach the correct palette to it. For more information
  331.   on default application palettes, see Technote #211. }
  332.  
  333.     VAR
  334.         palette:            PaletteHandle;
  335.         oldPerm:            Boolean;
  336.  
  337.     BEGIN
  338.         IF NOT gHasDefaultApplicationPalettes THEN BEGIN
  339.             oldPerm := PermAllocation(TRUE);
  340.             palette := GetNewPalette(gPaletteIDToUse);
  341.             oldPerm := PermAllocation(oldPerm);
  342.             FailNil(palette);
  343.             SetPalette(theWindow.fWMgrWindow, palette, kDontWantUpdates);
  344.         END;
  345.     END;
  346.  
  347. {-------------------------------------------------------------------------------------------}
  348. {$S AFields}
  349.  
  350. PROCEDURE ShowFracHeader(aTitle: Str255; VAR aRecord: FracRecord; PROCEDURE
  351.                          DoToField(fieldName: Str255; fieldAddr: Ptr; fieldType: Integer));
  352.  
  353. { We have several objects that have a FracRecord somewhere in their instance variables. This
  354.   routine is called in their .Inspect methods to print out the various fields. }
  355.  
  356.     TYPE
  357.         FracRecordPtr        = ^FracRecord;
  358.  
  359.     BEGIN
  360.         DoToField(aTitle, NIL, bTitle);
  361.         DoToField('  fType', @aRecord.fType, bOSType);
  362.         DoToField('  hdrId', @aRecord.hdrId, bHexInteger);
  363.         DoToField('  version', @aRecord.version, bInteger);
  364.         DoToField('  done', @aRecord.done, bBoolean);
  365.         DoToField('  elapsed', @aRecord.elapsed, bLongInt);
  366.         DoToField('  startingTime', @aRecord.startingTime, bLongInt);
  367.         DoToField('  endingTime', @aRecord.endingTime, bLongInt);
  368.         DoToField('  areaComplete', @aRecord.areaComplete, bLongInt);
  369.         DoToField('  use32BitCQD', @aRecord.use32BitCQD, bBoolean);
  370.         DoToField('  pages', NIL, bTitle);
  371.         DoToField('    realMin', @aRecord.pages.RealMin, bExtended);
  372.         DoToField('    realMax', @aRecord.pages.RealMax, bExtended);
  373.         DoToField('    imagMin', @aRecord.pages.ImagMin, bExtended);
  374.         DoToField('    imagMax', @aRecord.pages.ImagMax, bExtended);
  375.         DoToField('    multiPaging', @aRecord.pages.multiPaging, bBoolean);
  376.         DoToField('    currentH', @aRecord.pages.currentH, bInteger);
  377.         DoToField('    currentV', @aRecord.pages.currentV, bInteger);
  378.         DoToField('    maxH', @aRecord.pages.maxH, bInteger);
  379.         DoToField('    maxV', @aRecord.pages.maxH, bInteger);
  380.         DoToField('  deltaP', @aRecord.deltaP, bExtended);
  381.         DoToField('  deltaQ', @aRecord.deltaQ, bExtended);
  382.         DoToField('  plotWidth', @aRecord.plotWidth, bLongInt);
  383.         DoToField('  plotHeight', @aRecord.plotHeight, bLongInt);
  384.         DoToField('  calcRect', @aRecord.calcRect, bRect);
  385.     END;
  386.  
  387.  
  388. {-------------------------------------------------------------------------------------------}
  389. {$S ARea}
  390.  
  391. PROCEDURE StripString(VAR aString: Str255);
  392.  
  393. { Utility routine that strips leading and trailing spaces. This is used by the time printing
  394.   routines to remove mornStr and eveStr, wherever they may be. }
  395.  
  396.     VAR
  397.         i: integer;
  398.  
  399.     BEGIN
  400.         i := LENGTH(aString);
  401.         REPEAT
  402.             IF aString[i] = ' ' THEN
  403.                 i := i - 1;
  404.         UNTIL (aString[i] <> ' ');
  405.         aString[0] := char(i);
  406.  
  407.         i := 1;
  408.         REPEAT
  409.             IF aString[i] = ' ' THEN
  410.                 i := i + 1;
  411.         UNTIL (aString[i] <> ' ');
  412.         IF i > 1 THEN
  413.             Delete(aString, 1, i - 1);
  414.     END;
  415.  
  416. {-------------------------------------------------------------------------------------------}
  417. {------------------------------TFracAppApplication Methods----------------------------------}
  418. {-------------------------------------------------------------------------------------------}
  419. {$S AInit}
  420.  
  421. PROCEDURE TFracAppApplication.IFracAppApplication(itsMainFileType: OSType);
  422.  
  423. { Initializes the application and globals. Called by the main application when the
  424.   applicaton object is created. }
  425.  
  426.     CONST
  427.         kZeroTime            = 0;
  428.  
  429.     VAR
  430.         palette:            PaletteHandle;
  431.         i:                    Integer;
  432.  
  433.     BEGIN
  434.         fWindowList := NIL;
  435.         SELF.IApplication(itsMainFileType);
  436.  
  437.         fWindowList := NewList;
  438.  
  439.     { Collect some information needed for working with the time string items in the
  440.       infobar. In order to convert an elapsed time, in seconds, into a time string,
  441.       we use IUTimePString. We pass to it a modified Intl0 Handle, that has no
  442.       morning and evening string attribute, and will display the time starting at
  443.       00:00:00, rather than 12:00:00 or 24:00:00. This modified Intl0 Handle is
  444.       saved in “gIntlHandle” }
  445.  
  446.         gIntlHandle := IUGetINTL(0);
  447.         FailNil(gIntlHandle);
  448.         FailOSErr(HandToHand(Handle(gIntlHandle)));
  449.  
  450.         WITH Intl0Hndl(gIntlHandle)^^ DO BEGIN
  451.             timeCycle := zeroCycle;
  452.             mornStr := '    ';
  453.             eveStr := '    ';
  454.         END;
  455.  
  456.         IUTimePString(kZeroTime, kWantSeconds, gMaxWidthTimeString, gIntlHandle);
  457.         StripString(gMaxWidthTimeString);
  458.  
  459.     { Set gRebuildWindowsMenu to TRUE to force the Windows menu to be rebuilt for
  460.       the first time. This is important, so that our dummy menu item gets removed.
  461.       Also, call CmdToMenuItem to find the menu item of our first window entry in
  462.       the menu. }
  463.  
  464.         gRebuildWindowsMenu := TRUE;
  465.         CmdToMenuItem(cFirstWindowBase, gWindowMenuNumber, gFirstWindowBase);
  466.  
  467.     { If 32-bit Color QuickDraw is around, we make a note that we could like to use
  468.       it for our offscreen routines as a default. However, this can be turned off
  469.       if we’d like to offscreen things the old fashioned way with a flick of a menu
  470.       item. Also, decide which palette we are going to use for our windows,
  471.       depending on if we have 32-bit Color QuickDraw. If  32-bit Color QuickDraw is
  472.       not around, then show an alert that tells the user that some features will
  473.       not be available. }
  474.  
  475.         gUse32BitCQD := gConfiguration.has32BitQD;
  476.  
  477.         IF gConfiguration.has32BitQD THEN BEGIN
  478.             gPaletteIDToUse := kAnimatingPalette;
  479.         END
  480.         ELSE BEGIN
  481.             gPaletteIDToUse := kTolerantPalette;
  482.             StdAlert(kNo32BCQD);
  483.         END;
  484.  
  485.     { We’d like to take advantage to default application palettes if the system
  486.       supports them (see technote #211). To condition our program in the places
  487.       necessary to support default application palettes, we set this global
  488.       variable when running on the appropriate systems. }
  489.  
  490.         gHasDefaultApplicationPalettes := (gConfiguration.systemVersion >= $0602);
  491.  
  492.     { We can calculate our fractals in one of two different ways. There is a
  493.       pixel-by-pixel method, which is good for complicated fractals, and there is
  494.       another method that incurs some additional overhead, but more than makes this
  495.       up in optimizations on documents that have some large areas that are all the
  496.       same color. By default, we use the latter, faster method. }
  497.  
  498.         gUseFastAlgorithm := TRUE;
  499.  
  500.     { Set the default print resolution to 72 dpi. This in anticipation of the day
  501.       when this program will be flexible enough to calculate at other resolutions.
  502.       However, for now, it’s unsupported. }
  503.  
  504.         gPrintResolution := 72;
  505.  
  506.     { Start off not animating, initialize the palettes to normal (not jumbled) and
  507.       set the last time that we did animate to sometime way in the past (Jan 1,
  508.       1904) }
  509.  
  510.         gPaletteIsJumbled := FALSE;
  511.         gAnimate := FALSE;
  512.         gLastAnimated := 0;
  513.  
  514.     { If we are not using the Performance tools, we can use the Time Manager for
  515.       apportioning our time, and for calculating the elapsed time to calculate a
  516.       fractal. If so, install a time manager task. If we are running under
  517.       6.0.3 or later, we can take advantage of the fact that the Time Manager
  518.       gives our time task procedure a pointer to our task record. Otherwise, we
  519.       have to fall back on method of remembering a reference to our global by hand.
  520.       See Technote #180, sub-section “Time Manager Tasks”, for more information on
  521.       this.
  522.  
  523.       Also note that when I install the time manager task, that I call my own glue
  524.       routine rather than just calling InsTime. This is because I have some special
  525.       Assembly language glue that calls InsXTime. This is a new time manager call
  526.       that offers a “no-drift” feature. In other words, the difference between
  527.       successive calls to my time manager procedure is exactly what I specified,
  528.       rather than what I specified, plus the overhead involved in the time manager,
  529.       plus the time it takes my time task procedure to execute. InsTimeNoDrift
  530.       works in such a way that it will automatically take advantage of this feature
  531.       if it is there. }
  532.  
  533. {$IFC NOT qPerform}
  534.         gTMTask.A5 := GetA5;
  535.         IF gConfiguration.systemVersion < $0603 THEN BEGIN
  536.             gTMTask.taskRecord.tmAddr := @TimeCounterThatFetchesItsOwnTaskPtr;
  537.             InitCounter(@gTMTask.taskRecord);
  538.         END
  539.         ELSE BEGIN
  540.             gTMTask.taskRecord.tmAddr := @TimeCounter;
  541.         END;
  542.  
  543.         gCounter := 0;
  544.         InsTimeNoDrift(@gTMTask.taskRecord);
  545.         PrimeTime(@gTMTask.taskRecord, 1);
  546. {$ENDC}
  547.  
  548.         IF gDeadStripSuppression THEN BEGIN
  549.             IF Member(TObject(NIL), TFracAppWindow) THEN;
  550.             IF Member(TObject(NIL), TFracAppView) THEN;
  551.             IF Member(TObject(NIL), TNoFlashStaticText) THEN;
  552.         END;
  553.  
  554.         SELF.CreateManyColors;
  555.  
  556.     { Associate one of our palettes with the clipboard window. This is so that we
  557.       can see our fractal properly if we copy them to the desk scrap. }
  558.  
  559.         MySetPalette(gClipWindow);
  560.  
  561.     END;                                            { TFracAppApplication.IFracAppApplication
  562.                                                      }
  563.  
  564. {-------------------------------------------------------------------------------------------}
  565. {$S AWriteFile}
  566.  
  567. PROCEDURE TFracAppApplication.AutoSaveThisGuy(doneDoc: TFracAppDocument);
  568.  
  569. { This is just a simple routine that gets called when we are done calculating a fractal
  570.   document that is part of a multipaging process. It creates a file name based on what
  571.   part of the overall fractal it represents, and saves everything out to that file. It then
  572.   saves some information so that we can know where to create any subsequent documents, and
  573.   closes the document. }
  574.  
  575.     CONST
  576.         kDontAskForFileName = FALSE;
  577.         kNotMakingCopy        = FALSE;
  578.  
  579.     VAR
  580.         Hpage, Vpage:        Str255;
  581.  
  582.     BEGIN
  583.  
  584.     { First, save off and close the previous document. We title the thing “Set- x,y”, and
  585.       tell MacApp to jam it to whatever directory we happen to be set to. }
  586.  
  587.         NumToString(gPageRecord.currentH, Hpage);
  588.         NumToString(gPageRecord.currentV, Vpage);
  589.         doneDoc.SetTitle(ConCat('Set- ', Hpage, ',', Vpage));
  590.         {$Push} {$H-}                                { Pascal will complain about fVolRefNum
  591.                                                      otherwise. }
  592.         FailOSErr(GetVol(NIL, doneDoc.fVolRefNum));
  593.         {$Pop}
  594.  
  595.     { Before we close the document, we have to leave some information laying around
  596.       so we know how to create the next one. All of this information is
  597.       conveniently kept in the ‘pages’ field of the FracHeader. So we just save it
  598.       in a global PageRecord variable. }
  599.  
  600.         gPageRecord := doneDoc.GetFracHeader.pages;
  601.  
  602.     { Save the document with no questions asked, using the name of the document
  603.       that was set before.    If we have a problem with the file, an alert will come
  604.       up freezing the operation for user input, but that is OK.  We at least try to
  605.       be automatic, but if something freaks out it is OK to pause. }
  606.  
  607.         doneDoc.Save(cSave, kDontAskForFileName, kNotMakingCopy);
  608.         doneDoc.Close;
  609.     END;
  610.  
  611. {-------------------------------------------------------------------------------------------}
  612. {$S ARes}
  613.  
  614. PROCEDURE TFracAppApplication.BuildWindowsMenu;
  615.  
  616. { We have a special kind of window that notifies our application when it opens or closes
  617.   itself. Our application then either adds or removes that window from a list it keeps.
  618.   Also, if a window renames itself (say, the owning document was saved), it notifies the
  619.   application. In both cases, this method gets called to totally rebuild the Windows menu.
  620.   It also matches up the top window with a menu item, and puts a check mark by it. }
  621.  
  622.     VAR
  623.         windowsMenu:        MenuHandle;
  624.         curItem:            Integer;
  625.         nItems:             Integer;
  626.  
  627.     PROCEDURE AppendWindowToMenu(theWindow: TObject);
  628.  
  629.     { This sub-procedure is responsible for adding the name of a window to the
  630.       menu. It is called by the TList.Each method for each item in the list. We
  631.       coerce the object to a TWindow, get the window’s title, and append it to the
  632.       window’s menu. Note how we do this: we first add an item with a dummy name,
  633.       and then rename it with SetItem. This is to avoid the Menu Manager’s
  634.       interpreting things like “/,” “<,” “!,” and other meta characters. }
  635.  
  636.         VAR
  637.             itsTitle:            Str255;
  638.  
  639.         BEGIN
  640.             TWindow(theWindow).GetTitle(itsTitle);
  641.             AppendMenu(windowsMenu, 'x');
  642.             SetItem(windowsMenu, curItem, itsTitle);
  643.             curItem := curItem + 1;
  644.         END;
  645.  
  646.     BEGIN
  647.         IF gRebuildWindowsMenu THEN BEGIN
  648.  
  649.             windowsMenu := GetMHandle(gWindowMenuNumber);
  650.             nItems := CountMItems(windowsMenu);
  651.  
  652.             IF nItems >= gFirstWindowBase THEN BEGIN
  653.                 FOR curItem := nItems DOWNTO gFirstWindowBase DO BEGIN
  654.  
  655.                     DelMenuItem(windowsMenu, curItem);
  656.  
  657.                 { The Menu Manager has this feature (yes, it’s really a feature), where it sets
  658.                   the top bit of enableFlags when you call DelMenuItem. It does this so that
  659.                   any items that are being scrolled into the enableFlags range will
  660.                   automatically be enabled. This is being consistant with the fact that all
  661.                   menu items beyond 32 are enabled. Anyway, it messes us up BuildWindowsMenu,
  662.                   because we call it in the middle of DoSetupMenus. If we delete all menu items
  663.                   here, we would like to have the Windows menu title greyed out. However, that
  664.                   won’t happen, because DelMenuItem set some bits in enableFlags, and MacApp
  665.                   won’t gray out the menu title. So we compensate for that by disabling the
  666.                   32nd menu item by hand. }
  667.  
  668.                     DisableItem(windowsMenu, 31);
  669.  
  670.                 END;
  671.             END;
  672.  
  673.             curItem := gFirstWindowBase;
  674.             fWindowList.Each(AppendWindowToMenu);
  675.             gRebuildWindowsMenu := FALSE;
  676.  
  677.         END;
  678.     END;
  679.  
  680. {-------------------------------------------------------------------------------------------}
  681. {$S ATerminate}
  682.  
  683. PROCEDURE TFracAppApplication.Close; OVERRIDE;
  684.  
  685. { The Close method to allow us to clean up before the application quits. We override this
  686.   method rather than TFracAppApplication.Free, because .Free rarely gets called. The
  687.   only time it can get called is when TApplication.Run returns to the main program. At that
  688.   point, it is the main procedure’s responsibility to call .Free. However, hardly anyone
  689.   ever does that, so we override TFracAppApplication.Close instead, which gets called when
  690.   the user selects Quit.
  691.  
  692.   Our Quit item just removes the time manager task. Failure to do so will leave a pointer to
  693.   a no-longer existant task record and task procedure. Once another application is launched,
  694.   these will get overridden in memory, and the time manager will crash your machine with
  695.   glee. }
  696.  
  697.     BEGIN
  698.         INHERITED Close;
  699. {$IFC NOT qPerform}
  700.         RmvTime(@gTMTask.taskRecord);
  701. {$ENDC}
  702.     END;                                            { TFracAppApplication.Close }
  703.  
  704. {-------------------------------------------------------------------------------------------}
  705. {$S AInit}
  706.  
  707. PROCEDURE TFracAppApplication.CreateManyColors;
  708.  
  709. { Called during the initialization of our application to set up some color stuff. First of
  710.   all, if we can take advantage of the default application palette, we get the palette,
  711.   and set set it as the default. Note that it’s documented in technote #211 that we can just
  712.   define a ‘pltt’ resource of ID 0, and that will automatically be used as the default
  713.   application palette. However, through the wonders of System Software Engineering, that no
  714.   longer works on a Mac IIci and System 6.0.4. Explicitly setting the application palette is
  715.   the workaround. But I digress...After we set the palette, we read in and save a reference
  716.   to the color table used for our offscreen devices and palette manager animation stuff. }
  717.  
  718.     VAR
  719.         oldPerm:            Boolean;
  720.  
  721.     BEGIN
  722.  
  723.     { We can take advantage of default application palettes under System 6.0.2 or
  724.       later. So check for this before trying to set such a palette. If we don’t
  725.       support application palettes, MySetPalette will kick in when we create a
  726.       window and attach an individual palette to each window. }
  727.  
  728.         IF gHasDefaultApplicationPalettes THEN BEGIN
  729.             oldPerm := PermAllocation(TRUE);
  730.             gPalette := GetNewPalette(gPaletteIDToUse);
  731.             oldPerm := PermAllocation(oldPerm);
  732.             FailNil(gPalette);
  733.             SetPalette(WindowPtr( - 1), gPalette, kDontWantUpdates);
  734.         END;
  735.  
  736.     { Now allocate a color table that we will use whenever we create a new
  737.       document. This is so we have the same color table for each document. }
  738.  
  739.         oldPerm := PermAllocation(TRUE);
  740.         gOurColors := GetCTable(kClut);
  741.         oldPerm := PermAllocation(oldPerm);
  742.         FailNilResource(gOurColors);
  743.     END;
  744.  
  745. {-------------------------------------------------------------------------------------------}
  746. {$S ARes}
  747.  
  748. FUNCTION TFracAppApplication.DoIdle(phase: IdlePhase): Boolean; OVERRIDE;
  749.  
  750. { Performs Idle time processing for the application. This will do the fractal calculation
  751.   during idle time. It will allow each open document a chance to calculate.  The way we
  752.   do this is in a front to back order. We get a reference to the top document. If it is not
  753.   done, we call its engine to calculate a little bit of the fractal, and then get out
  754.   of here. If the top document IS done, we find the next document behind it, and give
  755.   it some time, if needed. This process is carried all the way back for as many documents as
  756.   we have.
  757.  
  758.   This routine is also responsible for doing the Palette Manager animation, if the user has
  759.   selected that option. Finally, if a document signals that it’s done, and that document is
  760.   part of a multipage set, we save that document to disk and start a new one. }
  761.  
  762.     VAR
  763.         documentToCalculate: TFracAppDocument;
  764.         documentToAnimate:    TFracAppDocument;
  765.         theDocumentFinishedCalculating: Boolean;
  766.  
  767.     FUNCTION EachWMgrWindowDoTil(FUNCTION DoToWMgrWindow(theWMgrWindow: WindowPtr): Boolean):
  768.                                  WindowPtr;
  769.  
  770.     { Iterate over the windows on the screen, looking for one that satisfies a
  771.       certain condition. This condition is defined by the procedure passed in to
  772.       us. We send to the window pointer, and it returns either yea or nay. If nay,
  773.       then we get another window. If yea, then we return the window pointer to the
  774.       caller. If no windows get a yea, then we return NIL. }
  775.  
  776.         VAR
  777.             aWindowPtr:         WindowPtr;
  778.             done:                Boolean;
  779.  
  780.         BEGIN
  781.             EachWMgrWindowDoTil := NIL;
  782.             aWindowPtr := GetWindowList;
  783.             done := FALSE;
  784.             WHILE (aWindowPtr <> NIL) & NOT done DO BEGIN
  785.                 done := DoToWMgrWindow(aWindowPtr);
  786.                 IF done THEN
  787.                     EachWMgrWindowDoTil := aWindowPtr
  788.                 ELSE
  789.                     aWindowPtr := WindowPtr(WindowPeek(aWindowPtr)^.nextWindow);
  790.             END;
  791.         END;
  792.  
  793.     PROCEDURE GetDocumentToCalculate(VAR docToCalculate: TFracAppDocument);
  794.  
  795.     { Looks for a document that needs calculation time. It does this by calling
  796.       EachWMgrWindowDoTil with a procedure that looks at the document attached to
  797.       that window, and asks if it needs calculation time. If it does, then we
  798.       return that document to the caller. If we don’t find one, we return NIL. }
  799.  
  800.         VAR
  801.             windowToCalculate:    WindowPtr;
  802.             theTWindow:         TWindow;
  803.  
  804.         FUNCTION FoundFirstUnfinishedTFracAppWindow(theWMgrWindow: WindowPtr): Boolean;
  805.  
  806.             BEGIN
  807.                 theTWindow := WMgrToWindow(theWMgrWindow);
  808.                 FoundFirstUnfinishedTFracAppWindow := (theTWindow <> NIL) &
  809.                                                       Member(theTWindow, TFracAppWindow) &
  810.                                                       NOT TFracAppDocument(theTWindow.
  811.                                                       fDocument).GetDone;
  812.             END;
  813.  
  814.         BEGIN
  815.             windowToCalculate := EachWMgrWindowDoTil(FoundFirstUnfinishedTFracAppWindow);
  816.             IF windowToCalculate <> NIL THEN
  817.                 docToCalculate := TFracAppDocument(theTWindow.fDocument)
  818.             ELSE
  819.                 docToCalculate := NIL;
  820.         END;
  821.  
  822.     PROCEDURE GetDocumentToAnimate(VAR docToAnimate: TFracAppDocument);
  823.  
  824.     { Finds a document that will do some animation for us. This routine calls
  825.       EachWMgrWindowDoTil to find the first TFracAppWindow it can, and returns the
  826.       attached document. If there are no such windows, then return NIL. }
  827.  
  828.         VAR
  829.             windowToAnimate:    WindowPtr;
  830.             theTWindow:         TWindow;
  831.  
  832.         FUNCTION FoundTopTFracAppWindow(theWMgrWindow: WindowPtr): Boolean;
  833.  
  834.             BEGIN
  835.                 theTWindow := WMgrToWindow(theWMgrWindow);
  836.                 FoundTopTFracAppWindow := (theTWindow <> NIL) & Member(theTWindow,
  837.                                           TFracAppWindow);
  838.             END;
  839.  
  840.         BEGIN
  841.             windowToAnimate := EachWMgrWindowDoTil(FoundTopTFracAppWindow);
  842.             IF windowToAnimate <> NIL THEN
  843.                 docToAnimate := TFracAppDocument(theTWindow.fDocument)
  844.             ELSE
  845.                 docToAnimate := NIL;
  846.         END;
  847.  
  848.     BEGIN
  849.  
  850.         GetDocumentToCalculate(documentToCalculate);
  851.         GetDocumentToAnimate(documentToAnimate);
  852.  
  853.         IF documentToAnimate <> NIL THEN
  854.             documentToAnimate.AnimateColors;
  855.  
  856.         IF documentToCalculate <> NIL THEN
  857.             theDocumentFinishedCalculating := documentToCalculate.CalcTown;
  858.  
  859.         IF (documentToCalculate = NIL) & (documentToAnimate = NIL) THEN
  860.             SELF.SetIdleFreq(kMaxIdleTime);
  861.  
  862.     { See if we are doing multipage operation, and if so, save the document, close
  863.       the window and start a new one. }
  864.  
  865.         IF (documentToCalculate <> NIL) & theDocumentFinishedCalculating &
  866.            (documentToCalculate.IsMultiPaging) THEN BEGIN
  867.             SELF.AutoSaveThisGuy(documentToCalculate);
  868.             SELF.MakeNewGibbleyFromGPageRecord;
  869.         END;
  870.  
  871.     { Idle Time handler have to return a boolean value that states whether or not
  872.       they have removed themselves from memory. This was something that was
  873.       supported transparently in MacApp 1.x, but things have changed since then.
  874.       Now that MacApp 2.0 is MultiFinder aware, it tries to be more intelligent
  875.       about giving time up to background apps. Because of this, it manages the time
  876.       given to idle time handlers more thoroughly. It maintains fields that say how
  877.       often they should be called, and when the last time one was called. Normally,
  878.       it tries to set this latter field when the TEvtHandler.DoIdle method is done.
  879.       However, what happens if the object removed itself from the chain and freed
  880.       itself from memory? The reference to the object is no longer valid, and
  881.       MacApp would be writing to random memory. To prevent this from happening, we
  882.       tell MacApp if we are still around. If we return TRUE, MacApp knows that we
  883.       are history. }
  884.  
  885.         DoIdle := FALSE;
  886.  
  887.     END;                                            { TFracAppApplication.DoIdle }
  888.  
  889. {-------------------------------------------------------------------------------------------}
  890. {$S AOpen}
  891.  
  892. FUNCTION TFracAppApplication.DoMakeDocument(itsCmdNumber: CmdNumber): TDocument; OVERRIDE;
  893.  
  894. { Launches a TFracAppDocument; called when application’s icon is opened, or when New, New
  895.   From Selection, New MultiPage, or Open is requested by the user. After it is done, the
  896.   view and window can be created, relying upon the data in the document. Every application
  897.   which uses documents MUST override this method }
  898.  
  899.     VAR
  900.         aFracAppDocument:    TFracAppDocument;
  901.  
  902.     BEGIN
  903.  
  904.         { Allocate and initialize the document. }
  905.  
  906.         New(aFracAppDocument);
  907.         FailNil(aFracAppDocument);
  908.  
  909.     { Now initialize the document fields, and set up the global state of the
  910.       fractal to a default set of the starting fractal. We specify here what kind
  911.       of TOffscreen object to create in IFracAppDocument. In order to use
  912.       TNewCoolOffscreen (ie, the new 32-bit CQD stuff), we both have to have 32-bit
  913.       CQD and want to use it. This could be specified by a menu item, for example. }
  914.  
  915.         aFracAppDocument.IFracAppDocument(gConfiguration.has32BitQD & gUse32BitCQD,
  916.                                           itsCmdNumber);
  917.  
  918.     { We successfully created a document so we can return the document object for
  919.       use by the application. }
  920.  
  921.         DoMakeDocument := aFracAppDocument;
  922.  
  923.     { Set the idle time so that we start calculating now! }
  924.  
  925.         SELF.SetIdleFreq(0);
  926.     END;                                            { TFracAppApplication.DoMakeDocument }
  927.  
  928. {-------------------------------------------------------------------------------------------}
  929. {$S ADoCommand}
  930.  
  931. FUNCTION TFracAppApplication.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
  932.  
  933. { Handles the menu selections that have global thermonuclear impact. In this case, we handle
  934.   the menu items that determine the characteristics of the next fractal document we create
  935.   (dpi settings, calculation method, or use of 32 Bit CQD), and the Windows menu. Note that
  936.   we have to do some special handling of the Windows menu, as MacApp knows nothing about it;
  937.   we created that puppy ourselves. }
  938.  
  939.     BEGIN
  940.         DoMenuCommand := gNoChanges;
  941.  
  942.     { Check to see if the user selected one of the window names that is dynamically
  943.       added to the Windows menu. It does this in two ways. First, it checks to see
  944.       if this is a menu command number that MacApp doesn’t know about. In other
  945.       words, is this a menu that didn’t start off life in a ‘cmnu’ resource? If
  946.       not, then we added this menu item by hand with Menu Manager calls, and it
  947.       could be a Windows menu item. We also need to check to see if the command
  948.       number passed to us is the same as the dummy menu item we installed to
  949.       determine the location of the window menu items. This is because the first
  950.       window menu item will replace that dummy item, and assume its command number.
  951.       If either of these cases is true, then call DoWindowsMenu to deal with
  952.       bringing the window forward. }
  953.  
  954.         IF (aCmdNumber < 0) | (aCmdNumber = cFirstWindowBase) THEN BEGIN
  955.  
  956.             SELF.DoWindowsCommand(aCmdNumber)
  957.  
  958.         END
  959.         ELSE BEGIN
  960.  
  961.             { A menu item that MacApp knows about was selected. }
  962.  
  963.             CASE aCmdNumber OF
  964.  
  965.             { Determine whether or not to use the 32 Bit CQD routines for our offscreen
  966.               creation and management. If not, we’ll use some home grown routines. }
  967.  
  968.                 cUse32CQD: BEGIN
  969.                     gUse32BitCQD := TRUE;
  970.                 END;
  971.                 cUseHomebrew: BEGIN
  972.                     gUse32BitCQD := FALSE;
  973.                 END;
  974.  
  975.             { Set the printing/calculation resolution. This would be handy for generating
  976.               some 300 dpi pictures for laserprinters and such. }
  977.  
  978.                 cMake72dpi: BEGIN
  979.                     gPrintResolution := 72;
  980.                 END;
  981.                 cMake300dpi: BEGIN
  982.                     gPrintResolution := 300;
  983.                 END;
  984.  
  985.             { Which algorithm to use. Either calculate everything a pixel at a time, or use
  986.               the Mariani/Silver algorithm to preflight certain solid areas. }
  987.  
  988.                 cSlowAlgorithm: BEGIN
  989.                     gUseFastAlgorithm := FALSE;
  990.                 END;
  991.                 cFastAlgorithm: BEGIN
  992.                     gUseFastAlgorithm := TRUE;
  993.                 END;
  994.  
  995.             { E. None of the above. }
  996.  
  997.                 OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber);
  998.             END;
  999.         END;
  1000.     END;
  1001.  
  1002. {-------------------------------------------------------------------------------------------}
  1003. {$S ARes}
  1004.  
  1005. PROCEDURE TFracAppApplication.DoSetupMenus; OVERRIDE;
  1006.  
  1007. { Determines the state of our menus. Before MacApp calls DoSetupMenus, it goes through and
  1008.   disables all the menu items, and removes any check marks. Then, starting with gTarget, it
  1009.   calls DoSetupMenus. Each DoSetupMenus should call its INHERITED method first so that they
  1010.   can set up the world the way the like it, and then we have a chance to override that
  1011.   (there’s that word again...). In this case, TFracAppApplication deals with all menu items
  1012.   that have a global aspect (like the options for creating a new document), but aren’t
  1013.   handled by TApplication. }
  1014.  
  1015.     VAR
  1016.         windowsMenu:        MenuHandle;
  1017.         i:                    Integer;
  1018.         topTWindow:         TWindow;
  1019.         windowNumber:        Integer;
  1020.  
  1021.     BEGIN
  1022.  
  1023.         INHERITED DoSetupMenus;                     { Do mainline stuff first. }
  1024.  
  1025.         EnableCheck(cUse32CQD, gConfiguration.has32BitQD, gConfiguration.has32BitQD &
  1026.                     gUse32BitCQD);
  1027.         EnableCheck(cUseHomebrew, TRUE, NOT gUse32BitCQD);
  1028.         EnableCheck(cMake72dpi, FALSE, (gPrintResolution = 72));
  1029.         EnableCheck(cMake300dpi, FALSE, (gPrintResolution = 300));
  1030.         EnableCheck(cSlowAlgorithm, TRUE, NOT gUseFastAlgorithm);
  1031.         EnableCheck(cFastAlgorithm, TRUE, gUseFastAlgorithm);
  1032.  
  1033.     { Make sure that our Windows menu is up to date. BuildWindowsMenu is our own
  1034.       routine the will see if the Windows menu needs updating. If so, it will tear
  1035.       it down, and rebuild it with the current list of windows. }
  1036.  
  1037.         SELF.BuildWindowsMenu;
  1038.  
  1039.     { Enable all of the items in the Window Menu. Also, match up the top window
  1040.       with its menu item, and put a check mark by the menu item. }
  1041.  
  1042.         windowsMenu := GetMHandle(gWindowMenuNumber);
  1043.         IF NOT fWindowList.IsEmpty THEN BEGIN
  1044.             FOR i := gFirstWindowBase TO (gFirstWindowBase+fWindowList.GetSize-1) DO BEGIN
  1045.                 EnableItem(windowsMenu, i);
  1046.             END;
  1047.         END;
  1048.  
  1049.         topTWindow := SELF.WMgrToWindow(FrontWindow);
  1050.         IF topTWindow <> NIL THEN BEGIN
  1051.             windowNumber := fWindowList.GetSameItemNo(topTWindow);
  1052.             IF windowNumber > 0 THEN BEGIN
  1053.                 CheckItem(windowsMenu, gFirstWindowBase + windowNumber - 1, TRUE);
  1054.             END;
  1055.         END;
  1056.  
  1057.     END;
  1058.  
  1059. {-------------------------------------------------------------------------------------------}
  1060. {$S ARes}
  1061.  
  1062. PROCEDURE TFracAppApplication.DoShowAboutApp; OVERRIDE;
  1063.  
  1064. { Show our own About box rather than MacApp’s fine but rather standard one. We simply use a
  1065.   MacApp view and window, and call PoseModally on its TDialogView. }
  1066.  
  1067.     VAR
  1068.         theWindow:            TWindow;
  1069.         theWindow2:         TWindow;
  1070.         dismisser:            IDType;
  1071.         dismisser2:         IDType;
  1072.  
  1073.     BEGIN
  1074.         theWindow := TWindow(NewTemplateWindow(kAbout, NIL));
  1075.         REPEAT
  1076.             dismisser := TDialogView(theWindow.FindSubView('DLOG')).PoseModally;
  1077.             IF dismisser = 'icon' THEN BEGIN
  1078.                 theWindow2 := TWindow(NewTemplateWindow(kAbout2, NIL));
  1079.                 dismisser2 := TDialogView(theWindow2.FindSubView('DLOG')).PoseModally;
  1080.                 theWindow2.Close;
  1081.             END;
  1082.         UNTIL dismisser = 'okok';
  1083.         theWindow.Close;
  1084.     END;                                            { TFracAppApplication.DoShowAboutApp }
  1085.  
  1086. {-------------------------------------------------------------------------------------------}
  1087. {$S ADoCommand}
  1088.  
  1089. PROCEDURE TFracAppApplication.DoWindowsCommand(aCmdNumber: CmdNumber);
  1090.  
  1091. { Sub-method to handle the Windows menu. Called by TFracAppApplication.DoMenuCommand to map
  1092.   the menu item selected to a window, and to bring that window to front. It does this by
  1093.   converting the menu item number into an index into the application’s fWindowList, and
  1094.   extracting that TWindow. When we get it, we bring it to front with the TWindow.Select
  1095.   method. }
  1096.  
  1097.     VAR
  1098.         menu, item:         Integer;
  1099.         chosenWindow:        TWindow;
  1100.  
  1101.     BEGIN
  1102.         CmdToMenuItem(aCmdNumber, menu, item);
  1103.         chosenWindow := TWindow(fWindowList.At(item - gFirstWindowBase + 1));
  1104.         IF qDebug THEN
  1105.             FailNil(chosenWindow);
  1106.         chosenWindow.Select;
  1107.     END;
  1108.  
  1109. {-------------------------------------------------------------------------------------------}
  1110. {$S ARes}
  1111.  
  1112. PROCEDURE TFracAppApplication.InstallWindowMenuItem(window: TWindow; install: Boolean);
  1113.  
  1114. { This method is called by TFracAppWindows when they either show or hide themselves. They
  1115.   pass in a reference to themselves (SELF), and whether they are showing or hiding
  1116.   themselves. This routine then either adds that window to or deletes it from a list it
  1117.   maintains for the purpose. After it’s done that, it flags the menus as dirty. }
  1118.  
  1119.     BEGIN
  1120.         IF install THEN
  1121.             fWindowList.InsertLast(window)
  1122.         ELSE
  1123.             fWindowList.AtDelete(fWindowList.GetSameItemNo(window));
  1124.  
  1125.         InvalidateMenus;
  1126.         gRebuildWindowsMenu := TRUE;
  1127.     END;                                            { InstallWindowMenuItem }
  1128.  
  1129. {-------------------------------------------------------------------------------------------}
  1130. {$S AOpen}
  1131.  
  1132. PROCEDURE TFracAppApplication.MakeNewGibbleyFromGPageRecord;
  1133.  
  1134. { Handy routine used when multi-paging. After the previous document has been closed, we want
  1135.   to make a new one. We figure out the coordinates of the next page to be done, and check to
  1136.   see if it is in range of our maximum page range. If not, we are all done, and don’t do
  1137.   anything. However, if we need to continue, we call OpenNew to kick off a new document. }
  1138.  
  1139.     VAR
  1140.         morePagesToMake:    Boolean;
  1141.         deltaReal, deltaImag: Extended;             { rectangle size when making new multiple
  1142.                                                      page doc. }
  1143.  
  1144.     BEGIN
  1145.  
  1146.     { Work on creating the new document. We base this on the values of RealMin,
  1147.       ImagMin, RealMax, and ImagMax that we save from the last document. These
  1148.       values are jammed into gPageRecord before the old document is erased from
  1149.       existence. }
  1150.  
  1151.         deltaReal := gPageRecord.RealMax - gPageRecord.RealMin;
  1152.         deltaImag := gPageRecord.ImagMax - gPageRecord.ImagMin;
  1153.  
  1154.         morePagesToMake := TRUE;
  1155.  
  1156.         WITH gPageRecord DO BEGIN
  1157.             IF currentH < maxH THEN BEGIN
  1158.                 currentH := currentH + 1;            { We just move one page to the right. }
  1159.  
  1160.                 ImagMin := ImagMax;
  1161.                 ImagMax := ImagMin + deltaImag;
  1162.             END
  1163.             ELSE BEGIN
  1164.  
  1165.             { If we are off the end of the maximum number of pages vertically,
  1166.               we are done, so we can just mark it as done and skip it. }
  1167.  
  1168.                 IF currentV < maxV THEN BEGIN
  1169.                     currentH := 1;                    { Move back to the left hand side. }
  1170.                     currentV := currentV + 1;        { and down a page. }
  1171.  
  1172.                     ImagMin := ImagMin - ((maxH - 1) * deltaImag);
  1173.                     ImagMax := ImagMin + deltaImag;
  1174.  
  1175.                     { Move down a page vertically, based on old position. }
  1176.                     RealMin := RealMax;
  1177.                     RealMax := RealMin + deltaReal;
  1178.                 END
  1179.                 ELSE BEGIN
  1180.                     morePagesToMake := FALSE;
  1181.                 END;                                { if done with page }
  1182.             END;                                    { if done with this row }
  1183.         END;                                        { with gPageRecord }
  1184.  
  1185.         IF morePagesToMake THEN BEGIN
  1186.             SELF.OpenNew(cContinuingMultiPage);
  1187.         END;
  1188.     END;                                            { MakeNewGibbleyFromGPageRecord }
  1189.  
  1190. {-------------------------------------------------------------------------------------------}
  1191. {$S AFields}
  1192.  
  1193. PROCEDURE TFracAppApplication.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  1194.                                                          fieldType: Integer)); OVERRIDE;
  1195.  
  1196.     BEGIN
  1197.         DoToField('TFracAppApplication', NIL, bClass);
  1198.         DoToField('fWindowList', @fWindowList, bObject);
  1199.  
  1200.         { Print fields of anscestors }
  1201.         INHERITED Fields(DoToField);
  1202.     END;                                            { TFracAppApplication.Fields }
  1203.  
  1204. {-------------------------------------------------------------------------------------------}
  1205. {---------------------------------TPICTDocument Methods-------------------------------------}
  1206. {-------------------------------------------------------------------------------------------}
  1207. {$S AOpen}
  1208.  
  1209. PROCEDURE TPICTDocument.IPICTDocument;
  1210.  
  1211. { Init for the TPICTDocument. We don’t have any variables to initialize, so just set up the
  1212.   TDocument object itself. }
  1213.  
  1214.     BEGIN
  1215.         SELF.IDocument(kFileType, kSignature, kUsesDataFork, NOT kUsesRsrcFork,
  1216.                        NOT kDataOpen, NOT kRsrcOpen);
  1217.     END;                                            { TPICTDocument.IPICTDocument }
  1218.  
  1219. {-------------------------------------------------------------------------------------------}
  1220. {$S AWriteFile}
  1221.  
  1222. PROCEDURE TPICTDocument.DoDrawPicture;
  1223.  
  1224. { Abstract method here. Because our TPICTDocument supports writing to the disk, we need some
  1225.   way to generate the drawing commands. However, the generic TPICTDocument knows nothing
  1226.   about how the data is stored or should be imaged. Therefore, we create the name of a
  1227.   method that should be called, and require that this method be overridden in order for it
  1228.   to have any effect. If this method is not overridden, the programmer gets a message in the
  1229.   debugger saying so. }
  1230.  
  1231.     BEGIN
  1232.         IF qDebug THEN
  1233.             ProgramBreak('TPICTDocument.DoDrawPicture: Gotta override me!');
  1234.     END;                                            { TPICTDocument.DoDrawPicture }
  1235.  
  1236. {-------------------------------------------------------------------------------------------}
  1237. {$S AWriteFile}
  1238.  
  1239. PROCEDURE PictSizer(dPointer: Ptr; nextHunk: Integer);
  1240.  
  1241. { This routine will size the current image as it goes to the disk. It won’t actually save
  1242.   any data or anything, but will merely watch the bytes go by keeping track of how many go
  1243.   by. The size is used by DoNeedDiskSpace. }
  1244.  
  1245.     BEGIN
  1246.         gPictSize := gPictSize + nextHunk;
  1247.     END;                                            { PictSizer }
  1248.  
  1249. {-------------------------------------------------------------------------------------------}
  1250. {$S AWriteFile}
  1251.  
  1252. PROCEDURE TPICTDocument.DoNeedDiskSpace(VAR dataForkBytes, rsrcForkBytes: LONGINT); OVERRIDE;
  1253.  
  1254. { Routine to find out how much disk space will be required to save the data. This does not
  1255.   call the Inherited DoNeedDiskSpace since we don’t support printing info here.  The routine
  1256.   will replace the PutPicProc of the port with our PictSizer routine. When the picture is
  1257.   created here, no bytes will actually be allocated or saved, we will just watch it go by
  1258.   and save off the size in the global variable. That value is returned as the expected
  1259.   document size, along with the size of the PICT user data area of 512 bytes. }
  1260.  
  1261.     VAR
  1262.         newProcs:            CQDProcs;
  1263.         oldProcs:            QDProcsPtr;             { bug in include files, CGrafPort has
  1264.                                                      QDProcs *** }
  1265.  
  1266.     BEGIN
  1267.  
  1268.     { Save off the old GrafProcs, and install a new set. This new set includes a
  1269.       pointer to PictSizer, which will be used to measure the size of the final
  1270.       PICT. When we are all done, we’ll re-install the normal set of GrafProcs. }
  1271.  
  1272.         SELF.SetRWGrafProcs(oldProcs, newProcs, NIL, @PictSizer);
  1273.  
  1274.     { Init the size of the pict we are going to save.  Start with picture header. }
  1275.  
  1276.         gPictSize := SizeOf(Picture);
  1277.  
  1278.     { Now go ahead and open the picture and build it in RAM. We would have done
  1279.       this by slices before, but the newer systems have a patch for playing back
  1280.       pictures that minimize the RAM hit, so we don’t have to worry about the full
  1281.       screen CopyBits here. }
  1282.  
  1283.         WITH thePort^ DO BEGIN
  1284.             gPictHandle := OpenPicture(portRect);
  1285.             SELF.DoDrawPicture;
  1286.             ClosePicture;                            { the picture is created, and packed. }
  1287.         END;                                        { with thePort^ }
  1288.  
  1289.    { Done saving the size of the picture itself.  Now set the GrafProcs back to normal. }
  1290.  
  1291.         thePort^.grafProcs := oldProcs;
  1292.  
  1293.     { Dispose the pict handle, we didn’t actually make anything there. }
  1294.  
  1295.         KillPicture(gPictHandle);
  1296.         gPictHandle := NIL;
  1297.  
  1298.     { The picture has been sized.  Now add that in to the total size the file will use on
  1299.       disk, include the header for the file, plus the number of bytes in actual PICT. }
  1300.  
  1301.         dataForkBytes := dataForkBytes + gPictSize + kPICTHeaderSize;
  1302.  
  1303.     END;                                            { TPICTDocument.DoNeedDiskSpace }
  1304.  
  1305. {-------------------------------------------------------------------------------------------}
  1306. {$S ARes}
  1307.  
  1308. PROCEDURE PictReader(dPointer: Ptr; nextHunk: Integer);
  1309.  
  1310. { Bottleneck routine to read the picture from the disk. This will read the data required,
  1311.   and pass it along to the unpacker. This makes it possible to avoid using any RAM for the
  1312.   actual reading part, as it is being played back into the offscreen device. Error handling
  1313.   is somewhat tricky, since we need to force the picture to finish, and there isn’t a really
  1314.   good way to do this. The desired attempt here is to pass back a “picture is finished”
  1315.   opcode ($00FF) so we can get back to our code to handle the error. This is better than no
  1316.   error recovery, but is not guaranteed to work. }
  1317.  
  1318.     VAR
  1319.         longHunk:            LONGINT;
  1320.         i:                    Integer;
  1321.  
  1322.     BEGIN
  1323.  
  1324.         IF gPictError = noErr THEN BEGIN
  1325.             longHunk := nextHunk;
  1326.             gPictError := FSRead(gPictRefNum, longHunk, dPointer);
  1327.         END;
  1328.  
  1329.     { Handle the error situation by passing back $00FF as the data. }
  1330.  
  1331.         IF gPictError <> noErr THEN BEGIN
  1332.             FOR i := 1 TO nextHunk DO BEGIN
  1333.                 IF ODD(i) THEN
  1334.                     dPointer^ := $00
  1335.                 ELSE
  1336.                     dPointer^ := - 1;
  1337.                 dPointer := Ptr(ORD4(dPointer) + 1);
  1338.             END;
  1339.         END;
  1340.  
  1341.     END;                                            { PictReader }
  1342.  
  1343. {-------------------------------------------------------------------------------------------}
  1344. {$S AReadFile}
  1345.  
  1346. PROCEDURE TPICTDocument.DoRead(aRefNum: Integer; rsrcExists, forPrinting: Boolean); OVERRIDE;
  1347.  
  1348. { Routine to read the data from the data fork of the file into our document so it can be
  1349.   displayed.  The quickdraw bottleneck will be replaced with the PictReader routine, making
  1350.   it read the data from the disk as the picture requests more data.  This obviates the need
  1351.   for an extra handle that is used to play back the picture. This is done since that extra
  1352.   handle can be on the order of 100K, memory we may not have available. }
  1353.  
  1354.     VAR
  1355.         anOffWorlder:        TOffscreen;
  1356.         tempRect:            Rect;
  1357.         recsize:            LONGINT;
  1358.         fi:                 FailInfo;
  1359.         newProcs:            CQDProcs;
  1360.         oldProcs:            QDProcsPtr;             { bug in include files, CGrafPort has
  1361.                                                      QDProcs *** }
  1362.  
  1363.     PROCEDURE DeathRead(error: OSErr; message: LONGINT);
  1364.  
  1365.         BEGIN
  1366.             IF gPictHandle <> NIL THEN
  1367.                 KillPicture(gPictHandle);
  1368.             gPictHandle := NIL;
  1369.         END;
  1370.  
  1371.     BEGIN
  1372.  
  1373.     { Make sure the file position is right at the start of the picture in the file. }
  1374.  
  1375.         FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize));
  1376.  
  1377.     { Allocate a small handle that will be used as the Pict handle for drawing from
  1378.       the disk.  This is just the picture header. }
  1379.  
  1380.         gPictHandle := PicHandle(NewHandle(SizeOf(Picture)));
  1381.         FailNil(gPictHandle);
  1382.  
  1383.     { If the read of the picture header fails, we want to dispose the handle allocated. }
  1384.  
  1385.         CatchFailures(fi, DeathRead);
  1386.  
  1387.     { Tell PictReader what file to read from. }
  1388.  
  1389.         gPictRefNum := aRefNum;
  1390.         gPictError := noErr;
  1391.  
  1392.     { Now fill in the picture header itself, using the data from the disk. }
  1393.  
  1394.         recsize := SizeOf(Picture);
  1395.         gPictError := FSRead(aRefNum, recsize, Ptr(gPictHandle^));
  1396.         FailOSErr(gPictError);
  1397.  
  1398.     { That is the only call we can’t recover from immediately, the rest of the
  1399.       routine is not easy to recover from, so we won’t go through DeathRead. }
  1400.  
  1401.         Success(fi);
  1402.  
  1403.     { The file position is right at the beginning of the picture data, so we can just
  1404.       install the bottleneck and call DrawPicture to fill our offscreen gDevice
  1405.       with the data that was saved.  Set to that port and gDevice for playback. }
  1406.  
  1407.     { Save the pointer to the current CGrafProcs, and
  1408.       the install our our Pict proc in the grafProcs. }
  1409.  
  1410.         SELF.SetRWGrafProcs(oldProcs, newProcs, @PictReader, NIL);
  1411.  
  1412.     { We can now draw the picture that will be read out of the file into this port
  1413.       in order to init the port for later use in updating the window.  We are already
  1414.       set to draw in the offscreen port.  Do the DrawPicture to have PictReader read
  1415.       the data out of the file while it is being played into the offscreen Port. }
  1416.  
  1417.         DrawPicture(gPictHandle, gPictHandle^^.picFrame);
  1418.  
  1419.     { Done reading the data of the picture itself.  Now set the GrafProcs back to normal. }
  1420.  
  1421.         thePort^.grafProcs := oldProcs;
  1422.  
  1423.     { Bag the handle we made for playing back the picture. }
  1424.  
  1425.         KillPicture(gPictHandle);
  1426.         gPictHandle := NIL;
  1427.  
  1428.     { If we had an error while reading the data, we must error out. }
  1429.  
  1430.         FailOSErr(gPictError);
  1431.  
  1432.     END;                                            { TPICTDocument.DoRead }
  1433.  
  1434. {-------------------------------------------------------------------------------------------}
  1435. {$S AWriteFile}
  1436.  
  1437. PROCEDURE PictWriter(dPointer: Ptr; nextHunk: Integer);
  1438.  
  1439. { This routine will save the current image as it is created.  As the data requests go by
  1440.   that data will be written to the file. The data is being created by the OpenPicture/
  1441.   CopyBits in DoWrite. This is the bottleneck for that operation. Any errors found while
  1442.   doing this will make us skip any further requests to write data to the disk. No memory is
  1443.   allocated. Communication with DoWrite is done through globals, since bottlenecks must be
  1444.   at the main level. The bottleneck must also keep track of how many bytes are written, so
  1445.   that the header on the picture can be fixed up to be correct. This must be done to avoid
  1446.   creating bogus pictures.    The picSize field of the handle must be updated continuously so
  1447.   that when the picture is done, the ClosePicture can create a valid picture. The check for
  1448.   the NIL handle is to handle the problem of when the OpenPicture is called.  The proc gets
  1449.   called before the handle is valid. Be very careful of these bottleneck things; it is easy
  1450.   to run into problems that are very hard to figure out. QuickDraw has no facilities to give
  1451.   you info when things go wrong so it makes it a bit tougher. }
  1452.  
  1453.     VAR
  1454.         longHunk:            LONGINT;
  1455.  
  1456.     BEGIN
  1457.         IF gPictError = noErr THEN BEGIN
  1458.             longHunk := nextHunk;
  1459.             gPictError := FSWrite(gPictRefNum, longHunk, dPointer);
  1460.             gPictSize := gPictSize + longHunk;
  1461.             IF gPictHandle <> NIL THEN
  1462.                 gPictHandle^^.picSize := LoWord(gPictSize);
  1463.         END;                                        { if no error so far }
  1464.     END;                                            { PictWriter }
  1465.  
  1466. {-------------------------------------------------------------------------------------------}
  1467. {$S AWriteFile}
  1468.  
  1469. PROCEDURE TPICTDocument.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
  1470.  
  1471. { Write the data calculated into the document to the file. This will make it a real PICT
  1472.   file. The file will be saved using QuickDraw Bottlenecks for the PutPicProc. As the data
  1473.   requests go by, they will be written to the file, using the PictWriter routine. }
  1474.  
  1475.     VAR
  1476.         recsize:            LONGINT;
  1477.         fi:                 FailInfo;
  1478.         newProcs:            CQDProcs;
  1479.         oldProcs:            QDProcsPtr;             { bug in include files, CGrafPort has
  1480.                                                      QDProcs *** }
  1481.  
  1482.     PROCEDURE DeathWrite(error: OSErr; message: LONGINT);
  1483.  
  1484.         BEGIN
  1485.             IF gPictHandle <> NIL THEN
  1486.                 KillPicture(gPictHandle);
  1487.             gPictHandle := NIL;
  1488.  
  1489.             thePort^.grafProcs := oldProcs;
  1490.         END;
  1491.  
  1492.     BEGIN
  1493.  
  1494.     { We need to write the picture data itself out to the file, after we set the
  1495.       mark to be after the entire header.  Make sure the file is that big before we
  1496.       do it. Included in this set is the header of the picture itself, the 10 bytes
  1497.       that include the rectangle. Those bytes will be updated after the picture is
  1498.       written. }
  1499.  
  1500.         FailOSErr(SetEOF(aRefNum, kPICTHeaderSize + SizeOf(Picture)));
  1501.         FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize + SizeOf(Picture)));
  1502.  
  1503.        { If the write of the picture header fails, we want to dispose the handle allocated. }
  1504.  
  1505.         CatchFailures(fi, DeathWrite);
  1506.  
  1507.     { The file is all set up to go.  We now want to replace the QuickDraw
  1508.       bottleneck and create the actual Picture data. }
  1509.  
  1510.         SELF.SetRWGrafProcs(oldProcs, newProcs, NIL, @PictWriter);
  1511.  
  1512.     { Tell PictWriter what file to write to, and start the pic size including the
  1513.       picture header.  Start all the pieces off the right way. }
  1514.  
  1515.         gPictRefNum := aRefNum;
  1516.         gPictSize := SizeOf(Picture);
  1517.         gPictError := noErr;
  1518.         gPictHandle := NIL;
  1519.  
  1520.     { Actually open the picture and do the CopyBits in order to process the picture.
  1521.       The data will be written by PictWriter as it is called by QuickDraw. }
  1522.  
  1523.         WITH thePort^ DO BEGIN
  1524.             gPictHandle := OpenPicture(portRect);
  1525.             ClipRect(portRect);                     { Make it a happier picture. }
  1526.  
  1527.             { copy all of the image to itself, in an open picture it saves the bits. }
  1528.  
  1529.             SELF.DoDrawPicture;
  1530.             ClosePicture;                            { the picture is created, and packed. }
  1531.         END;                                        { with thePort^ }
  1532.  
  1533.     { Now check for errors during the write operation. The gPictError field will be
  1534.       nonzero if we failed during the operation. }
  1535.  
  1536.         FailOSErr(gPictError);
  1537.  
  1538.     { Move back to front of file and write the valid picture info to file. }
  1539.  
  1540.         FailOSErr(SetFPos(aRefNum, fsFromStart, kPICTHeaderSize));
  1541.         recsize := SizeOf(Picture);
  1542.         FailOSErr(FSWrite(aRefNum, recsize, Ptr(gPictHandle^)));
  1543.  
  1544.     { Done saving the data of the picture itself.  Now set the GrafProcs back to
  1545.       normal. }
  1546.  
  1547.         thePort^.grafProcs := oldProcs;
  1548.  
  1549.     { Dispose the pict handle, we didn’t actually make anything there. }
  1550.  
  1551.         KillPicture(gPictHandle);
  1552.         gPictHandle := NIL;
  1553.  
  1554.     { If we lived through it, clear error handler. }
  1555.  
  1556.         Success(fi);
  1557.  
  1558.     END;                                            { TPICTDocument.DoWrite }
  1559.  
  1560. {-------------------------------------------------------------------------------------------}
  1561. {$S ARes}
  1562.  
  1563. PROCEDURE TPICTDocument.SetRWGrafProcs(VAR oldProcs: QDProcsPtr; VAR newProcs: CQDProcs;
  1564.                                        readProc, writeProc: ProcPtr);
  1565.  
  1566. { Common routine to set the current port’s grafprocs for reading and writing. It gets called
  1567.   by our DoRead and DoWrite overrides to that we can read and write PICTs without causing a
  1568.   big memory hit. }
  1569.  
  1570.     BEGIN
  1571.         oldProcs := thePort^.grafProcs;
  1572.         SetStdCProcs(newProcs);
  1573.         thePort^.grafProcs := @newProcs;
  1574.  
  1575.         IF readProc <> NIL THEN
  1576.             newProcs.GetPicProc := readProc;
  1577.  
  1578.         IF writeProc <> NIL THEN
  1579.             newProcs.PutPicProc := writeProc;
  1580.     END;
  1581.  
  1582. {-------------------------------------------------------------------------------------------}
  1583. {$S AFields}
  1584.  
  1585. PROCEDURE TPICTDocument.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  1586.                                                    fieldType: Integer)); OVERRIDE;
  1587.  
  1588.     BEGIN
  1589.         DoToField('TPICTDocument', NIL, bClass);
  1590.  
  1591.         { Print fields of anscestors }
  1592.         INHERITED Fields(DoToField);
  1593.     END;
  1594.  
  1595. {-------------------------------------------------------------------------------------------}
  1596. {--------------------------------TFracAppDocument Methods-----------------------------------}
  1597. {-------------------------------------------------------------------------------------------}
  1598. {$S AOpen}
  1599.  
  1600. PROCEDURE TFracAppDocument.IFracAppDocument(use32BitCQD: Boolean; startupMode: CmdNumber);
  1601.  
  1602. { Init for the FracAppDocument itself. We set a couple of fields to NIL so that we can Fail
  1603.   gracefully, and then init the base TPICTDocument. When we come back, we store in the
  1604.   FracHeader what kind of offscreen stuff we want to use. }
  1605.  
  1606.     BEGIN
  1607.  
  1608.         fOffWorlder := NIL;
  1609.         fFracAppEngine := NIL;
  1610.         fFracAppWindow := NIL;
  1611.         fStartupMode := startupMode;
  1612.  
  1613.         SELF.IPICTDocument;
  1614.  
  1615.     { We just initialize ‘use32BitCQD’ here because this is where we get passed its
  1616.       value. The rest of fFracHeader gets initialized in DoInitialState. }
  1617.  
  1618.         fFracHeader.use32BitCQD := use32BitCQD;
  1619.  
  1620.     END;                                            { TFracAppDocument.IFracAppDocument }
  1621.  
  1622. {-------------------------------------------------------------------------------------------}
  1623. {$S AClose}
  1624.  
  1625. PROCEDURE TFracAppDocument.Free; OVERRIDE;
  1626.  
  1627. { Free method for the documents themselves.  We need to override so that we can throw away
  1628.   the data object that was read in from the disk if it exists. }
  1629.  
  1630.     BEGIN
  1631.         SELF.FreeData;
  1632.         INHERITED Free;
  1633.     END;                                            { TFracAppDocument.Free }
  1634.  
  1635. {-------------------------------------------------------------------------------------------}
  1636. {$S ARes}
  1637.  
  1638. PROCEDURE TFracAppDocument.AnimateColors;
  1639.  
  1640. { Called during Idle time to do any palette manager animation that may be needed. Three
  1641.   conditions must be satisfied before we animate: 1) the user must select the right menu
  1642.   item, 2) we must be in the foreground, and 3) a decent amount of time has to have elapsed
  1643.   since we lat animated (we don’t want things to go whizzing by too fast on those faster
  1644.   Macs).
  1645.  
  1646.   If all of those conditions are satisfied, we animate. We first get a copy of the window’s
  1647.   current palette and put it into a color table with the Palette2Ctab call. We then shift up
  1648.   all the color entries, moving the one at the beginning to the end. Finally, we call
  1649.   AnimatePalette with our modified color table. }
  1650.  
  1651.     VAR
  1652.         currPalette:        PaletteHandle;
  1653.         destCTab:            CTabHandle;
  1654.         lastCSpec:            ColorSpec;
  1655.         theWindow:            WindowPtr;
  1656.         fi:                 FailInfo;
  1657.  
  1658.     PROCEDURE HdlAnimate(error: OSErr; message: LONGINT);
  1659.  
  1660.         BEGIN
  1661.             gAnimate := FALSE;
  1662.         END;
  1663.  
  1664.     BEGIN
  1665.         IF gAnimate & NOT gInBackGround & (TickCount > (gLastAnimated +
  1666.            kAnimationDelay)) THEN BEGIN
  1667.             theWindow := fFracAppWindow.fWMgrWindow;
  1668.  
  1669.         { Get the palette for our window. First attempt to get any palette that is
  1670.           directly attached to the window. If that doesn’t work, try to get the default
  1671.           application palette. Once we got one, convert it to a color table. }
  1672.  
  1673.             currPalette := GetPalette(theWindow);
  1674.             IF (currPalette = NIL) & gHasDefaultApplicationPalettes THEN
  1675.                 currPalette := GetPalette(WindowPtr( - 1));
  1676.             FailNil(currPalette);
  1677.  
  1678.             CatchFailures(fi, HdlAnimate);
  1679.             destCTab := CTabHandle(NewHandle(SizeOf(ColorTable) + ((kNumColors + 16) *
  1680.                                              SizeOf(ColorSpec))));
  1681.             FailNil(destCTab);
  1682.             Palette2CTab(currPalette, destCTab);
  1683.             Success(fi);
  1684.  
  1685.         { Move the colors around in the color table, skipping the first 16, and moving
  1686.           all the elements down by one, and copying the element at 16 back to the end
  1687.           of the table. The effect is to rotate the colors in the table. }
  1688.  
  1689.             {$Push} {$R-}                            { Turn off range checking for ctTable }
  1690.             lastCSpec := destCTab^^.ctTable[16];    { pull first one off. }
  1691.             BlockMove(@destCTab^^.ctTable[17], @destCTab^^.ctTable[16], (kNumColors - 1) *
  1692.                       SizeOf(ColorSpec));            { copy all one entry down. }
  1693.             destCTab^^.ctTable[kNumColors + 16 - 1] := lastCSpec; { put last color back on
  1694.                                                                    front. }
  1695.             {$Pop}
  1696.  
  1697.             AnimatePalette(theWindow, destCTab, 16, 16, kNumColors);
  1698.  
  1699.             DisposHandle(Handle(destCTab));
  1700.             gLastAnimated := TickCount;
  1701.         END;
  1702.     END;                                            { TFracAppDocument.AnimateColors }
  1703.  
  1704. {-------------------------------------------------------------------------------------------}
  1705. {$S ARes}
  1706.  
  1707. PROCEDURE TFracAppDocument.BumpAreaComplete(areaAmount: LONGINT);
  1708.  
  1709. { Adds “areaAmount” to the areaComplete field. We keep track of this stuff so that we can
  1710.   display the percentage complete. This is my own method that the TFracAppEngines call when
  1711.   they’ve updated a new area of the fractal. }
  1712.  
  1713.     BEGIN
  1714.         SELF.SetAreaComplete(fFracHeader.areaComplete + areaAmount, kDontForceUpdate);
  1715.     END;
  1716.  
  1717. {-------------------------------------------------------------------------------------------}
  1718. {$S ARes}
  1719.  
  1720. PROCEDURE TFracAppDocument.BumpChangeCount;
  1721.  
  1722. { Increments the fChangeCount field. This is my own method that the TFracAppEngines call
  1723.   when they’ve updated a new area of the fractal. }
  1724.  
  1725.     BEGIN
  1726.         SELF.SetChangeCount(SELF.GetChangeCount + 1);
  1727.     END;
  1728.  
  1729. {-------------------------------------------------------------------------------------------}
  1730. {$S ARes}
  1731.  
  1732. PROCEDURE TFracAppDocument.BumpCalculationTime(elapsedAmount: LONGINT);
  1733.  
  1734. { Increments the elapsedTime field and updates the window display. Called by the document
  1735.   idling routine: CalcTown. }
  1736.  
  1737.     BEGIN
  1738.         SELF.SetCalculationTime(fFracHeader.elapsed + elapsedAmount, kDontForceUpdate);
  1739.     END;
  1740.  
  1741. {-------------------------------------------------------------------------------------------}
  1742. {$S ARes}
  1743.  
  1744. FUNCTION TFracAppDocument.CalcTown: Boolean;
  1745.  
  1746. { Document’s idling routine. Called by TFracAppApplication.DoIdle when it’s this document’s
  1747.   turn to calculate part of itself. Takes care of calling the TFracAppEngine, and updating
  1748.   the window timer displays.
  1749.  
  1750.   It determines how much time to give to the FracAppEngine in 3 ways. First, if we are in
  1751.   the background, we don’t want to be CPU hogs, so we take up as little time as possible.
  1752.   Mostly this mean limiting ourselves to about one call to the engine. If we are in the
  1753.   foreground we have two ways of determining time. If we have built a version of our program
  1754.   that doesn’t include the Performance Tools, then we use the Time Manager for seeing how
  1755.   much time (in milliseconds) we’ve taken up, and leaving after a certain amount of time. If
  1756.   we built a version of the program that could possibly be using the performance tools, then
  1757.   we can’t take advantage of the Time Manager as the Performance tools pre-empt its use. So
  1758.   we time ourselves by using Ticks.
  1759.  
  1760.   We prefer using the Time Manager, though. This is because use use the timing results here
  1761.   are used to keep track of total elapsed time. The Time Manager’s resolution is 1
  1762.   millisecond (1/1000 of a second) the way we use it here, whereas using Ticks gives us a
  1763.   resolution of 1/60 of a second. After a while, little errors accumulate, which are
  1764.   aggravated by the low resolution of TickCount. So we use the Time Manager, instead, to
  1765.   lessen the accumulated errors. }
  1766.  
  1767.     VAR
  1768.         MaxInterval:        LONGINT;
  1769.         startTime:            LONGINT;
  1770.         endTime:            LONGINT;
  1771.         theDocumentFinishedCalculating: Boolean;
  1772.  
  1773.     BEGIN
  1774.         IF gInBackGround THEN
  1775.             MaxInterval := kBkCalcTime
  1776.         ELSE
  1777.             MaxInterval := kFgCalcTime;
  1778.  
  1779. {$IFC qPerform}
  1780.  
  1781.         startTime := TickCount;
  1782.         endTime := startTime + MaxInterval;
  1783.         SELF.PreDraw;
  1784.         REPEAT
  1785.             theDocumentFinishedCalculating := fFracAppEngine.CalcCity;
  1786.         UNTIL theDocumentFinishedCalculating OR (TickCount >= endTime);
  1787.         SELF.PostDraw;
  1788.         endTime := TickCount;
  1789.  
  1790. {$ELSEC}
  1791.  
  1792.         startTime := gCounter;
  1793.         endTime := startTime + MaxInterval;
  1794.         SELF.PreDraw;
  1795.         REPEAT
  1796.             theDocumentFinishedCalculating := fFracAppEngine.CalcCity;
  1797.         UNTIL theDocumentFinishedCalculating OR (gCounter >= endTime);
  1798.         SELF.PostDraw;
  1799.         endTime := gCounter;
  1800.  
  1801. {$ENDC}
  1802.  
  1803.     { We’re done with calculating our part of the fractal for now. Now update our
  1804.       counters in the window. }
  1805.  
  1806.         SELF.BumpCalculationTime(endTime - startTime);
  1807.         SELF.SetElapsedTime(kDontForceUpdate);
  1808.  
  1809.     { If the engine signalled that we’re all done, then remember that. Also return that fact
  1810.     to the routine that called us so that it can take appropriate action. }
  1811.  
  1812.         fFracHeader.done := theDocumentFinishedCalculating;
  1813.         CalcTown := theDocumentFinishedCalculating;
  1814.  
  1815.     END;
  1816.  
  1817. {-------------------------------------------------------------------------------------------}
  1818. {$S AWriteFile}
  1819.  
  1820. PROCEDURE TFracAppDocument.DoDrawPicture; OVERRIDE;
  1821.  
  1822. { This method overrides the abstract method in TPICTDocument. It is called in the writing
  1823.   routines to image the data for the PICT. }
  1824.  
  1825.     BEGIN
  1826.  
  1827.     { The next couple of lines are really weird. Take a look at pages V 71-72 of
  1828.       Inside Mac. There, in the description of CopyBits, you’ll see that it says
  1829.       that “During a CopyBits call, the foreground and background colors are
  1830.       applied to the image.” Technote 163, “Adding Color with CopyBits” explains
  1831.       more on exactly what happens. What my point is, though, is that we don’t want
  1832.       this to happen. So to effectively get a null color addition, we set the
  1833.       foreground and background colors to black and white. }
  1834.  
  1835.         RGBForeColor(gRGBBlack);
  1836.         RGBBackColor(gRGBWhite);
  1837.  
  1838.     { Copy all of the image to itself. This has no effect on the grafPort, but it
  1839.       does cause all of the data to get captured by the PICT grafProcs. }
  1840.  
  1841.         WITH thePort^ DO
  1842.             CopyBits(portBits, portBits, portRect, portRect, srcCopy, NIL);
  1843.     END;
  1844.  
  1845. {-------------------------------------------------------------------------------------------}
  1846. {$S AOpen}
  1847.  
  1848. PROCEDURE TFracAppDocument.DoInitialState; OVERRIDE;
  1849.  
  1850. { Does the work for a New operation, where we start with a new fractal that doesn’t have any
  1851.   stored data. This is to set up the view with no data and set up the fractal coordinates to
  1852.   the default. }
  1853.  
  1854.     VAR
  1855.         anOffWorlder:        TOffscreen;
  1856.         aFracAppEngine:     TFracAppEngine;
  1857.         tempRect:            Rect;
  1858.         versionToMake:        Integer;
  1859.  
  1860.     BEGIN
  1861.         WITH fFracHeader DO BEGIN
  1862.  
  1863.         { Start by filling in the fields that identify this document. These fields will
  1864.           be examined when we read the document back in from disk. If we don’t
  1865.           recognize them, we bail out of reading them back in. The fType field is set
  1866.           to our file’s signature. The hdrID identifies this header as one that is
  1867.           associated with FracApp. Finally, the version number identifies which FracApp
  1868.           engine we are using. At this point, we set it to kUnknownVersion, because we
  1869.           don’t know how the engine will identify itself; we rely on it to ultimately
  1870.           fill this field correctly. }
  1871.  
  1872.             fType := kSignature;
  1873.             hdrId := Integer(kHeaderID);
  1874.             version := kUnknownVersion;
  1875.  
  1876.             done := FALSE;                            { not done, starting brand new document.
  1877.                                                      }
  1878.             elapsed := 0;                            { just starting, set the time spent on
  1879.                                                      calculating the fractal to zip. }
  1880.             {$Push} {$H-}
  1881.             GetDateTime(startingTime);
  1882.             {$Pop}
  1883.             endingTime := 0;
  1884.             areaComplete := 0;
  1885.  
  1886.             IF fStartupMode IN [cNewFromSelection, cNewMultiPage,
  1887.                 cContinuingMultiPage] THEN BEGIN
  1888.  
  1889.             { We are being created from a previous document. This is either as a result of
  1890.               a multipage process, or because the user selected a range and told us to zoom
  1891.               in on it. In either case, all the information we need to boot up is in
  1892.               gPageRecord. }
  1893.  
  1894.                 pages := gPageRecord;
  1895.  
  1896.                 IF fStartupMode IN [cNewMultiPage, cContinuingMultiPage] THEN
  1897.                     pages.multiPaging := TRUE
  1898.                 ELSE
  1899.                     pages.multiPaging := FALSE;
  1900.  
  1901.             END
  1902.             ELSE BEGIN
  1903.  
  1904.             { We start from scratch. This is the standard set of coordinates to start the
  1905.               default Mandelbrot set. Set up the coordinates to do, saving state in header
  1906.               variables. The other variables (currentH, currentV, maxH, maxV) are not
  1907.               needed if we are not multipaging. Since this part of the IF/THEN statement
  1908.               doesn’t handle multipaging, we don’t have to futz with them. }
  1909.  
  1910.                   WITH pages DO BEGIN
  1911.                     RealMin := - 2.5;
  1912.                     RealMax := 1.5;
  1913.                     ImagMin := - 1.5232;
  1914.                     ImagMax := 1.5232;
  1915.                     multiPaging := FALSE;
  1916.                 END;
  1917.             END;
  1918.  
  1919.             { Set the fractal rectangle to be the full page size at 300 dpi. }
  1920.  
  1921.             {$Push} {$H-}
  1922.             SetRect(calcRect, 0, 0, kRectRight, kRectBottom);
  1923.             {$Pop}
  1924.  
  1925.         { Set up the width/height of fractal, the delta in each axis as a real number,
  1926.           and ensure that the starting min/max values for the figure are set to supply
  1927.           a 1:1 aspect ratio. }
  1928.  
  1929.         { Set up the iterations by calculating up the step constants, and the
  1930.           edges of the view area in pixels. }
  1931.  
  1932.             plotWidth := (calcRect.right - calcRect.left);
  1933.             plotHeight := (calcRect.bottom - calcRect.top);
  1934.             deltaP := (pages.RealMax - pages.RealMin) / (plotHeight - 1);
  1935.             deltaQ := (pages.ImagMax - pages.ImagMin) / (plotWidth - 1);
  1936.  
  1937.         { Force aspect ratio 1:1, making delta smallest of two.  This effectively grows
  1938.           one side or the other out, like rMax/iMax becoming bigger number. }
  1939.  
  1940.             IF deltaP > deltaQ THEN BEGIN
  1941.                 deltaQ := deltaP;
  1942.                 pages.ImagMax := deltaQ * (plotWidth - 1) + pages.ImagMin;
  1943.             END                                     { grow the q side }
  1944.             ELSE BEGIN
  1945.                 deltaP := deltaQ;
  1946.                 pages.RealMax := deltaP * (plotHeight - 1) + pages.RealMin;
  1947.             END;                                    { grow the p side }
  1948.         END;                                        { With FracHeader }
  1949.  
  1950.    { Create our supporting objects, the OffWorld object, and the Fractal engine object. }
  1951.  
  1952.         tempRect := fFracHeader.calcRect;
  1953.         anOffWorlder := SELF.MakeOffWorlder(fFracHeader.use32BitCQD, tempRect);
  1954.         fOffWorlder := anOffWorlder;
  1955.  
  1956.         CASE gUseFastAlgorithm OF
  1957.             TRUE: versionToMake := kFastVersion;
  1958.             FALSE: versionToMake := kNormalVersion;
  1959.         END;
  1960.  
  1961.         aFracAppEngine := SELF.MakeFracAppEngine(versionToMake);
  1962.         fFracAppEngine := aFracAppEngine;
  1963.  
  1964.     END;                                            { TFracAppDocument.DoInitialState }
  1965.  
  1966. {-------------------------------------------------------------------------------------------}
  1967. {$S AOpen}
  1968.  
  1969. PROCEDURE TFracAppDocument.DoMakeViews(forPrinting: Boolean); OVERRIDE;
  1970.  
  1971. { Make something to see our picture in. This uses NewTemplateWindow to create a window and
  1972.   its subviews from a ‘view’ resource. When we have the window, we read in a ‘pltt’ resource
  1973.   and attach it to it. We also initialize a lot of the windows fields with references to its
  1974.   subviews. Finally, we create a print handler, and attach it to the TFracAppView. }
  1975.  
  1976.     VAR
  1977.         theWindow:            TFracAppWindow;
  1978.         aHandler:            TStdPrintHandler;
  1979.  
  1980.     BEGIN
  1981.  
  1982.         theWindow := TFracAppWindow(NewTemplateWindow(kFracAppWindowID, SELF));
  1983.         fFracAppWindow := theWindow;
  1984.         MySetPalette(theWindow);
  1985.  
  1986.         WITH theWindow DO BEGIN
  1987.             fFracAppView := TFracAppView(FindSubView('FRAC'));
  1988.             fCTimeLabelView := TStaticText(FindSubView('tCTL'));
  1989.             fCTimeView := TStaticText(FindSubView('tCTm'));
  1990.             fETimeLabelView := TStaticText(FindSubView('tETL'));
  1991.             fETimeView := TStaticText(FindSubView('tETm'));
  1992.             fMethodNameView := TStaticText(FindSubView('tTYP'));
  1993.             fPercentView := TStaticText(FindSubView('tPCT'));
  1994.             fPercentLabelView := TStaticText(FindSubView('tPCL'));
  1995.             fSingleBarView := TControl(FindSubView('sBAR'));
  1996.         END;
  1997.  
  1998.         SELF.SetAreaComplete(fFracHeader.areaComplete, kForceUpdate);
  1999.         SELF.SetCalculationTime(fFracHeader.elapsed, kForceUpdate);
  2000.         SELF.SetElapsedTime(kForceUpdate);
  2001.         SELF.SetMethodName(kForceUpdate);
  2002.  
  2003.     { Printing handler as standard print handler will get a color port back
  2004.       from the driver.    The pixels are square, and the dimensions are fixed. }
  2005.  
  2006.         New(aHandler);
  2007.         FailNil(aHandler);
  2008.         aHandler.IStdPrintHandler(SELF,             { its document }
  2009.                                   fFracAppWindow.GetFracAppView,     { its view }
  2010.                                   kSquareDots,        { has square dots }
  2011.                                   kFixedSize,        { horzontal page size is fixed }
  2012.                                   kFixedSize);        { vertical page size is fixed }
  2013.  
  2014.     END;                                            { TFracAppDocument.DoMakeViews }
  2015.  
  2016. {-------------------------------------------------------------------------------------------}
  2017. {$S ADoCommand}
  2018.  
  2019. FUNCTION TFracAppDocument.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
  2020.  
  2021. { The document is in charge of menu items that affect the document. In FracApp, the only
  2022.   things that should be active when we have a document are the normal editting commands, and
  2023.   the palette munging stuff. We don’t support Cut or Paste, which don’t really make sense in
  2024.   FracApp, but we support Copy. Doing a Copy will put the current selection to the desk
  2025.   scrap as a PICT. We rely on the default MacApp clipboard routines to display the PICT. }
  2026.  
  2027.     BEGIN
  2028.         DoMenuCommand := gNoChanges;
  2029.         CASE aCmdNumber OF
  2030.  
  2031.             cAnimate: BEGIN
  2032.                 gAnimate := NOT gAnimate;
  2033.             END;
  2034.  
  2035.             cJumble: BEGIN
  2036.                 gPaletteIsJumbled := NOT gPaletteIsJumbled;
  2037.                 IF gPaletteIsJumbled THEN
  2038.                     SELF.JumblePalette
  2039.                 ELSE
  2040.                     SELF.RestorePalette;
  2041.             END;
  2042.  
  2043.             cNormal: BEGIN
  2044.                 gAnimate := FALSE;
  2045.                 gPaletteIsJumbled := FALSE;
  2046.                 SELF.RestorePalette;
  2047.             END;
  2048.  
  2049.             OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber);
  2050.         END;
  2051.     END;                                            { TFracAppDocument.DoMenuCommand }
  2052.  
  2053. {-------------------------------------------------------------------------------------------}
  2054. {$S AWriteFile}
  2055.  
  2056. PROCEDURE TFracAppDocument.DoNeedDiskSpace(VAR dataForkBytes,
  2057.     rsrcForkBytes: LONGINT); OVERRIDE;
  2058.  
  2059. { DoNeedDiskSpace is called when we need to estimate the size the file will be on disk. Most
  2060.   of the work is done by the base class, TPICTDocument. It figures out how large the file
  2061.   will be when converted into a PICT. However, before we can call it, we have to switch to
  2062.   the fractal image off screen. We then call the INHERITED method, and switch back after
  2063.   we’re all done. }
  2064.  
  2065.     BEGIN
  2066.         SELF.PreDraw;
  2067.         INHERITED DoNeedDiskSpace(dataForkBytes, rsrcForkBytes);
  2068.         SELF.PostDraw;
  2069.     END;                                            { TFracAppDocument.DoNeedDiskSpace }
  2070.  
  2071. {-------------------------------------------------------------------------------------------}
  2072. {$S AReadFile}
  2073.  
  2074. PROCEDURE TFracAppDocument.DoRead(aRefNum: Integer; rsrcExists,
  2075.     forPrinting: Boolean); OVERRIDE;
  2076.  
  2077. { Called to read in the file. We let the base class, TPICTDocument, handle reading in the
  2078.   actual PICT data. Here, we make sure that we are properly set to the offscreen world first
  2079.   so that we image to the right place. We also read in the user header data into our
  2080.   FracHeader record. }
  2081.  
  2082.     VAR
  2083.         recsize:            LONGINT;
  2084.         tempRect:            Rect;
  2085.         anOffWorlder:        TOffscreen;
  2086.         aFracAppEngine:     TFracAppEngine;
  2087.         fi:                 FailInfo;
  2088.  
  2089.     PROCEDURE DeathRead(error: OSErr; message: LONGINT);
  2090.  
  2091.         BEGIN
  2092.             SELF.PostDraw;
  2093.         END;
  2094.  
  2095.     BEGIN
  2096.  
  2097.     { The file is open already, we just have to read the data out of it. The first
  2098.       thing to read is the header we use to describe a fractal. If we get an error
  2099.       here we need to split since we should always have at least a header. The
  2100.       fractal header is the global state for the document. We just read it into the
  2101.       record and use it from there. }
  2102.  
  2103.         FailOSErr(SetFPos(aRefNum, fsFromStart, 0)); { starts at first byte of file. }
  2104.         recsize := SizeOf(fFracHeader);             { size of header on fractal files. }
  2105.         FailOSErr(FSRead(aRefNum, recsize, @fFracHeader));
  2106.  
  2107.     { We have the header for the PICT file. Now we need to be sure that it is a
  2108.       fractal document, and not something we can’t use. Check the header to be
  2109.       sure, and if not right, error out with a good alert message (using a standard
  2110.       MacApp errcode). }
  2111.  
  2112.         IF fFracHeader.fType <> kSignature THEN BEGIN
  2113.             IF qDebug THEN BEGIN
  2114.                 WrLblSig('Failing in DoRead. Signature', fFracHeader.fType);
  2115.                 writeln;
  2116.                 WrLblSig('                   should be', kSignature);
  2117.                 writeln;
  2118.             END;
  2119.             FailOSErr(errNotMyType);
  2120.         END;
  2121.  
  2122.         IF fFracHeader.hdrId <> Integer(kHeaderID) THEN BEGIN
  2123.             IF qDebug THEN BEGIN
  2124.                 WrLblHexInt('Failing in DoRead. hdrId', fFracHeader.hdrId);
  2125.                 writeln;
  2126.                 WrLblHexInt('               should be', Integer(kHeaderID));
  2127.                 writeln;
  2128.             END;
  2129.             FailOSErr(errNotMyType);
  2130.         END;
  2131.  
  2132.     { We have the data from the header. Go ahead and set up an offscreen world for
  2133.       this document, using the header rectangle. Once we got that, switch over to
  2134.       it, and read in the PICT stuff by calling the INHERITED DoRead method. }
  2135.  
  2136.         tempRect := fFracHeader.calcRect;
  2137.         anOffWorlder := SELF.MakeOffWorlder(fFracHeader.use32BitCQD, tempRect);
  2138.         fOffWorlder := anOffWorlder;
  2139.  
  2140.         CatchFailures(fi, DeathRead);
  2141.         SELF.PreDraw;
  2142.         INHERITED DoRead(aRefNum, rsrcExists, forPrinting);
  2143.         SELF.PostDraw;
  2144.         Success(fi);
  2145.  
  2146.     { Now we’ve read in the standard header, and have imaged the PICT into the
  2147.       offscreen PixMap. All we have to do now is create a fractal engine, and
  2148.       initialize from the rest of the data on this disk. }
  2149.  
  2150.         aFracAppEngine := SELF.MakeFracAppEngine(fFracHeader.version);
  2151.         fFracAppEngine := aFracAppEngine;
  2152.  
  2153.     { Set the file mark back to the end of the FracHeader. This is done as our last
  2154.       action so that anything subclassing us can read any information it appends to
  2155.       the normal FracHeader. It shouldn’t know what size the FracRecord is (or even
  2156.       really know that it exists), or realize that there is variable length engine
  2157.       data appended to it, so we reposition the file mark for it. }
  2158.  
  2159.         FailOSErr(SetFPos(aRefNum, fsFromStart, SizeOf(fFracHeader)));
  2160.         fFracAppEngine.DoRead(aRefNum, rsrcExists, forPrinting);
  2161.  
  2162.     END;                                            { TFracAppDocument.DoRead }
  2163.  
  2164. {-------------------------------------------------------------------------------------------}
  2165. {$S ARes}
  2166.  
  2167. PROCEDURE TFracAppDocument.DoSetupMenus; OVERRIDE;
  2168.  
  2169. { Set up the menus that the document is responsible for. These are the ones that logically
  2170.   should only be enabled if we have a document on the screen. In our case, this includes the
  2171.   Copy Edit command, and the Palette Manager animation commands. }
  2172.  
  2173.     BEGIN
  2174.         INHERITED DoSetupMenus;                     { Do mainline stuff first. }
  2175.  
  2176.         EnableCheck(cAnimate, gConfiguration.has32BitQD, gAnimate);
  2177.         EnableCheck(cJumble, gConfiguration.has32BitQD, gPaletteIsJumbled);
  2178.         Enable(cNormal, gConfiguration.has32BitQD);
  2179.     END;                                            { TFracAppDocument.DoSetupMenus }
  2180.  
  2181. {-------------------------------------------------------------------------------------------}
  2182. {$S AWriteFile}
  2183.  
  2184. PROCEDURE TFracAppDocument.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
  2185.  
  2186. { Called to write out the file. We let the base class, TPICTDocument, handle writing out the
  2187.   actual PICT data. Here, we make sure that we are properly set to the offscreen world first
  2188.   so that we copybits from the right place. We also write out the user header data from our
  2189.   FracHeader record and fractal engine. }
  2190.  
  2191.     VAR
  2192.         recsize:            LONGINT;
  2193.         fi:                 FailInfo;
  2194.  
  2195.     PROCEDURE DeathWrite(error: OSErr; message: LONGINT);
  2196.  
  2197.         BEGIN
  2198.             fOffWorlder.ChangeCTFlag(kAnimationFlag, kSetFlag);
  2199.             SELF.PostDraw;
  2200.         END;
  2201.  
  2202.     BEGIN
  2203.  
  2204.     { Prepare our world for writing the PICT out. First, we do a CatchFailures so
  2205.       that we can restore the world to a reasonable state in case something goes
  2206.       wrong. Then we switch in the offscreen world. The next step is kind of ugly,
  2207.       but necessary. We clear bit 14 of ctFlags before writing out the file. This
  2208.       is because we’ll run into problems later if we try to read the PICT back in
  2209.       with that bit set; we won’t match the colors that we would like in our
  2210.       offscreen world.
  2211.  
  2212.       Bit 14 is the bit that tells 32-bit Color QuickDraw to match palette entries
  2213.       when copying from one pixmap to another. However, when we are reading from
  2214.       disk, the destination is our offscreen pixmap, which doesn’t have a palette.
  2215.       If bit 14 is set, 32bCQD will fall back on a backup plan of trying to match
  2216.       RGB values. So our solution is to clear bit 14. Finally, we call the
  2217.       INHERITED method to write out the PICT. After that’s done, we put everything
  2218.       back, and proceed to the part where we write out the header info. }
  2219.  
  2220.         CatchFailures(fi, DeathWrite);
  2221.         SELF.PreDraw;                                { Swap in our offscreen world }
  2222.         fOffWorlder.ChangeCTFlag(kAnimationFlag, kClearFlag);
  2223.         INHERITED DoWrite(aRefNum, makingCopy);
  2224.         fOffWorlder.ChangeCTFlag(kAnimationFlag, kSetFlag);
  2225.         SELF.PostDraw;
  2226.         Success(fi);
  2227.  
  2228.     { We have legit data in our document, set the mark in the file to be at the front.
  2229.       and write out the header information. Once that’s done, we call the Fractal
  2230.       engine to write out anything that it needs to. Notice that we do this last, so
  2231.       that we leave the file mark at the next available spot to write. This is so any
  2232.       thing that sub-classes us will be able to write right where we left them. }
  2233.  
  2234.         FailOSErr(SetFPos(aRefNum, fsFromStart, 0));
  2235.         recsize := SizeOf(fFracHeader);
  2236.         FailOSErr(FSWrite(aRefNum, recsize, @fFracHeader));
  2237.         fFracAppEngine.DoWrite(aRefNum, makingCopy);
  2238.  
  2239.     END;                                            { TFracAppDocument.DoWrite }
  2240.  
  2241. {-------------------------------------------------------------------------------------------}
  2242. {$S ARes}
  2243.  
  2244. PROCEDURE TFracAppDocument.FreeData; OVERRIDE;
  2245.  
  2246. { This is typically used in a Revert case which is not really meaningful here, but the
  2247.   structure is the same so we use it anyway.  Frees the data associated with a document,
  2248.   that is strictly program data, not MacApp data. }
  2249.  
  2250.     BEGIN
  2251.         FreeIfObject(fOffWorlder);
  2252.         FreeIfObject(fFracAppEngine);
  2253.     END;                                            { TFracAppDocument.FreeData }
  2254.  
  2255. {-------------------------------------------------------------------------------------------}
  2256. { Access methods. These are here so that objects can query the document for some bits of
  2257.   information. Rather than have those objects just look into our instance variables and pull
  2258.   out what they need, we have them call these methods. This is so the actual implementation
  2259.   of the document data is kept private and, hence, flexible. For instance, an earlier
  2260.   version of this program had TFracAppView go into TFracAppDocument and take what it needed.
  2261.   This was done for speed purposes, as calling a function to return a value for you is much
  2262.   slower than just getting it yourself. However, this caused problems when the world changed
  2263.   with FracApp 2.0. Suddenly, document data was managed with TOffscreen objects. This
  2264.   necessitated modifying TFracAppView because the data wasn’t where it thought it was any
  2265.   more. Only the document should know how its data is managed and stored. Now that
  2266.   TFracAppView uses these accessors, we hopefully won’t have to make any changes to it in
  2267.   the future. }
  2268. {-------------------------------------------------------------------------------------------}
  2269. {$S ARes}
  2270.  
  2271. FUNCTION TFracAppDocument.GetDone: Boolean;
  2272.  
  2273.     BEGIN
  2274.         GetDone := fFracHeader.done;
  2275.     END;
  2276.  
  2277. {-------------------------------------------------------------------------------------------}
  2278. {$S ARes}
  2279.  
  2280. FUNCTION TFracAppDocument.GetFracHeader: FracRecord;
  2281.  
  2282.     BEGIN
  2283.         GetFracHeader := fFracHeader;
  2284.     END;
  2285.  
  2286. {-------------------------------------------------------------------------------------------}
  2287. {$S ARes}
  2288.  
  2289. FUNCTION TFracAppDocument.GetFracPort: CGrafPtr;
  2290.  
  2291.     BEGIN
  2292.         GetFracPort := fOffWorlder.GetOffPort;
  2293.     END;
  2294.  
  2295. {-------------------------------------------------------------------------------------------}
  2296. {$S ARes}
  2297.  
  2298. FUNCTION TFracAppDocument.GetOffPixBase: Ptr;
  2299.  
  2300. { Returns the pointer to the bits for the offscreen port. Called by the TFracAppEngine when
  2301.   doing its own version of GetCPixel. }
  2302.  
  2303.     BEGIN
  2304.         GetOffPixBase := fOffworlder.GetOffPixBase;
  2305.     END;
  2306.  
  2307. {-------------------------------------------------------------------------------------------}
  2308. {$S ARes}
  2309.  
  2310. FUNCTION TFracAppDocument.GetPlotHeight: LONGINT;
  2311.  
  2312.     BEGIN
  2313.         GetPlotHeight := fFracHeader.plotHeight;
  2314.     END;
  2315.  
  2316. {-------------------------------------------------------------------------------------------}
  2317. {$S ARes}
  2318.  
  2319. FUNCTION TFracAppDocument.GetPlotWidth: LONGINT;
  2320.  
  2321.     BEGIN
  2322.         GetPlotWidth := fFracHeader.plotWidth;
  2323.     END;
  2324.  
  2325. {-------------------------------------------------------------------------------------------}
  2326. {$S ARes}
  2327.  
  2328. FUNCTION TFracAppDocument.IsMultiPaging: Boolean;
  2329.  
  2330.     BEGIN
  2331.         IsMultiPaging := fFracHeader.pages.multiPaging;
  2332.     END;
  2333.  
  2334. {-------------------------------------------------------------------------------------------}
  2335. {$S ARes}
  2336.  
  2337. PROCEDURE TFracAppDocument.JumblePalette;
  2338.  
  2339. { Called in response to the user selecting the “Jumble Palette” menu item. This takes our
  2340.   color table, rotates every third color 65 position up, another every third color 65
  2341.   positions down, and leaves the rest alone. It then calls AnimatePalette with this rotated
  2342.   color table. }
  2343.  
  2344.     VAR
  2345.         newcolors:            CTabHandle;
  2346.         tempRGB:            RGBColor;
  2347.         i:                    Integer;
  2348.  
  2349.     BEGIN
  2350.         newcolors := gOurColors;
  2351.         FailOSErr(HandToHand(Handle(newcolors)));
  2352.         FailNil(newcolors);
  2353.         WITH newcolors^^ DO BEGIN
  2354.             FOR i := 0 TO (63 DIV 3) DO BEGIN
  2355.                 {[f-]} {$Push}{$R-}
  2356.                 tempRGB                 := ctTable[i*3+1].rgb;
  2357.                 ctTable[i*3+1].rgb        := ctTable[i*3+66].rgb;
  2358.                 ctTable[i*3+66].rgb     := ctTable[i*3+131].rgb;
  2359.                 ctTable[i*3+131].rgb    := tempRGB;
  2360.  
  2361.                 tempRGB                 := ctTable[i*3+132].rgb;
  2362.                 ctTable[i*3+132].rgb    := ctTable[i*3+67].rgb;
  2363.                 ctTable[i*3+67].rgb     := ctTable[i*3+2].rgb;
  2364.                 ctTable[i*3+2].rgb        := tempRGB;
  2365.                 {[f+]} {$Pop}
  2366.             END;
  2367.         END;
  2368.         AnimatePalette(fFracAppWindow.fWMgrWindow, newcolors, 1, 16, kNumColors);
  2369.         DisposHandle(Handle(newcolors));
  2370.     END;
  2371.  
  2372. {-------------------------------------------------------------------------------------------}
  2373. {$S ARes}
  2374.  
  2375. PROCEDURE TFracAppDocument.LockThePixels(lockIt: Boolean);
  2376.  
  2377. { Called by the TFracAppView before it does any CopyBits operations. This is so the View
  2378.   only needs to know about the TFracAppDocument; it never needs to know that a TOffScreen
  2379.   object exists. }
  2380.  
  2381.     BEGIN
  2382.         IF lockIt THEN
  2383.             fOffWorlder.LockThePixels
  2384.         ELSE
  2385.             fOffWorlder.UnlockThePixels;
  2386.     END;
  2387.  
  2388. {-------------------------------------------------------------------------------------------}
  2389. {$S ARes}
  2390.  
  2391. FUNCTION TFracAppDocument.MakeFracAppEngine(version: Integer): TFracAppEngine;
  2392.  
  2393. { All of the know-how that generates a fractal is encapsulated in the TFracAppEngine object.
  2394.   We’ll create a TNormalFracAppEngine to calculate the fractal on a pixel-by-pixel basis, or
  2395.   a TFastFracAppEngine if we want to use the Mariani/Silver algorithm. This method creates
  2396.   the object based on the version number passed to it, and return it to the caller. }
  2397.  
  2398.     VAR
  2399.         aNormalFracAppEngine: TNormalFracAppEngine;
  2400.         aFastFracAppEngine: TFastFracAppEngine;
  2401.  
  2402.     BEGIN
  2403.         CASE version OF
  2404.             kNormalVersion: BEGIN
  2405.                 New(aNormalFracAppEngine);
  2406.                 FailNil(aNormalFracAppEngine);
  2407.                 aNormalFracAppEngine.INormalFracAppEngine(SELF);
  2408.                 MakeFracAppEngine := aNormalFracAppEngine;
  2409.             END;
  2410.             kFastVersion: BEGIN
  2411.                 New(aFastFracAppEngine);
  2412.                 FailNil(aFastFracAppEngine);
  2413.                 aFastFracAppEngine.IFastFracAppEngine(SELF);
  2414.                 MakeFracAppEngine := aFastFracAppEngine;
  2415.             END;
  2416.             OTHERWISE BEGIN
  2417.                 IF qDebug THEN
  2418.                     writeln('Failing in MakeFracAppEngine. version = ', version);
  2419.                 FailOSErr(errNotMyType);
  2420.             END;
  2421.         END;
  2422.     END;                                            { TFracAppDocument.MakeFracAppEngine }
  2423.  
  2424. {-------------------------------------------------------------------------------------------}
  2425. {$S ARes}
  2426.  
  2427. FUNCTION TFracAppDocument.MakeOffWorlder(use32BitCQD: Boolean; bounds: Rect): TOffscreen;
  2428.  
  2429. { All the offscreen management will be handled by a TOffscreen object. Depending on whether
  2430.   or not 32-bit Color QuickDraw is available, we will either create an object that knows how
  2431.   to handle the new calls, or one that does things the old fashioned way. Once we have one
  2432.   of those, we use it for creating our offworld and for preparing it for being drawn into. }
  2433.  
  2434.     VAR
  2435.         aNewOffWorlder:     TNewCoolOffscreen;
  2436.         anOldOffWorlder:    TOldGrossOffscreen;
  2437.  
  2438.     BEGIN
  2439.         IF (use32BitCQD & gConfiguration.has32BitQD) THEN BEGIN
  2440.             New(aNewOffWorlder);
  2441.             FailNil(aNewOffWorlder);
  2442.             aNewOffWorlder.INewCoolOffscreen(bounds, gOurColors);
  2443.             MakeOffWorlder := aNewOffWorlder;
  2444.         END
  2445.         ELSE BEGIN
  2446.             New(anOldOffWorlder);
  2447.             FailNil(anOldOffWorlder);
  2448.             anOldOffWorlder.IOldGrossOffscreen(bounds, gOurColors);
  2449.             MakeOffWorlder := anOldOffWorlder;
  2450.         END;
  2451.     END;                                            { TFracAppDocument.MakeOffWorlder }
  2452.  
  2453. {-------------------------------------------------------------------------------------------}
  2454. {$S ARes}
  2455.  
  2456. PROCEDURE TFracAppDocument.PreDraw;
  2457.  
  2458. { Called by TFracAppEngine before it images to the offscreen bitmap. This is so the Engine
  2459.   only needs to know about the TFracAppDocument; it never needs to know that a TOffScreen
  2460.   object exists. }
  2461.  
  2462.     BEGIN
  2463.         fOffWorlder.PreDraw;
  2464.     END;
  2465.  
  2466. {-------------------------------------------------------------------------------------------}
  2467. {$S ARes}
  2468.  
  2469. PROCEDURE TFracAppDocument.PostDraw;
  2470.  
  2471. { Access method to our offscreen management to restore our original world that was swapped
  2472.   out with TFracAppDocument.PreDraw. }
  2473.  
  2474.     BEGIN
  2475.         fOffWorlder.PostDraw;
  2476.     END;
  2477.  
  2478. {-------------------------------------------------------------------------------------------}
  2479. {$S ARes}
  2480.  
  2481. PROCEDURE TFracAppDocument.ReportRectCompleted(dirtyRect: Rect);
  2482.  
  2483. { Called by the TFracAppEngine when an area of the fractal has been completed and needs to
  2484.   be copied to the screen. }
  2485.  
  2486.     BEGIN
  2487.         fFracAppWindow.GetFracAppView.InvalidRect(dirtyRect);
  2488.         WITH dirtyRect DO BEGIN
  2489.             SELF.BumpAreaComplete((right - left) * (bottom - top));
  2490.         END;
  2491.     END;
  2492.  
  2493. {-------------------------------------------------------------------------------------------}
  2494. {$S ARes}
  2495.  
  2496. PROCEDURE TFracAppDocument.RestorePalette;
  2497.  
  2498. { This effectively nullifies the effects of AnimateColors or JumblePalette. It makes sure
  2499.   that the color tables for our offscreen world and our gDevices match again. }
  2500.  
  2501.     BEGIN
  2502.         AnimatePalette(fFracAppWindow.fWMgrWindow, gOurColors, 1, 16, kNumColors);
  2503.     END;
  2504.  
  2505. {-------------------------------------------------------------------------------------------}
  2506. {$S ARes}
  2507.  
  2508. PROCEDURE TFracAppDocument.SetAreaComplete(newComplete: LONGINT; forceUpdate: Boolean);
  2509.  
  2510. { Updates the areaCompleted field in fFracHeader. Also updates the percentage field in the
  2511.   window. }
  2512.  
  2513.     VAR
  2514.         newPctComplete:     Integer;
  2515.         oldPctComplete:     Integer;
  2516.         pictureArea:        LONGINT;
  2517.         pctString:            Str255;
  2518.  
  2519.     BEGIN
  2520.         WITH fFracHeader DO BEGIN
  2521.             pictureArea := plotWidth * plotHeight;
  2522.             oldPctComplete := (areaComplete * 100) DIV pictureArea;
  2523.             newPctComplete := (newComplete * 100) DIV pictureArea;
  2524.             areaComplete := newComplete;
  2525.         END;
  2526.  
  2527.         IF forceUpdate | (oldPctComplete <> newPctComplete) THEN BEGIN
  2528.             NumToString(newPctComplete, pctString);
  2529.             fFracAppWindow.GetPercentView.SetText(pctString, kRedraw);
  2530.         END;
  2531.     END;
  2532.  
  2533. {-------------------------------------------------------------------------------------------}
  2534. {$S ARes}
  2535.  
  2536. PROCEDURE TFracAppDocument.SetCalculationTime(newElapsed: LONGINT; forceUpdate: Boolean);
  2537.  
  2538. { Updates the elapsedTime field in fFracHeader. Also updates the calculation time field in
  2539.   the window’s infobar. }
  2540.  
  2541.     VAR
  2542.         timeString:         Str255;
  2543.         newTime:            Longint;
  2544.  
  2545.     BEGIN
  2546.  
  2547.     { Find the new time, and compare it with the time that currently appears in the
  2548.       window. If they are different then call IUTimePString to convert the time
  2549.       into a string, and then update the string in the window.
  2550.  
  2551.       kUnitsPerTime is a constant that converts our counter into a number expressed
  2552.       in seconds. Our counters are either expressed in Ticks or milliseconds.
  2553.       kUnitsPerTime is the right number to convert them to seconds. }
  2554.  
  2555.         newTime := newElapsed DIV kUnitsPerSecond;
  2556.  
  2557.         IF forceUpdate | (newTime > (fFracHeader.elapsed DIV kUnitsPerSecond)) THEN BEGIN
  2558.             IUTimePString(newTime, kWantSeconds, timeString, gIntlHandle);
  2559.             fFracAppWindow.GetCTimeView.SetText(timeString, kRedraw);
  2560.         END;
  2561.  
  2562.         fFracHeader.elapsed := newElapsed;
  2563.  
  2564.     END;
  2565.  
  2566. {-------------------------------------------------------------------------------------------}
  2567. {$S ARes}
  2568.  
  2569. PROCEDURE TFracAppDocument.SetElapsedTime(forceUpdate: Boolean);
  2570.  
  2571. { Updates the elapsed time field in the window’s infobar based on the starting and current
  2572.   time. }
  2573.  
  2574.     CONST
  2575.         kWantSeconds        = TRUE;
  2576.  
  2577.     VAR
  2578.         timeString:         Str255;
  2579.         newElapsed:         LONGINT;
  2580.         currentTime:        LONGINT;
  2581.  
  2582.     BEGIN
  2583.  
  2584.     { Determine what to use for our “ending value” for the elapsed time. Normally,
  2585.       this is just the current time. However, if the document is done, then the
  2586.       counters should be stopped, in which case, the “ending value” is the last
  2587.       value stored in fFracHeader.endingTime. }
  2588.  
  2589.         IF SELF.GetDone THEN
  2590.             currentTime := fFracHeader.endingTime
  2591.         ELSE
  2592.             GetDateTime(currentTime);
  2593.         newElapsed := currentTime - fFracHeader.startingTime;
  2594.  
  2595.     { If the current elapsed time is different from the time displayed in the
  2596.       window, convert it into a string with IUTimePString, and update the string in
  2597.       the window. }
  2598.  
  2599.         IF forceUpdate | (currentTime > fFracHeader.endingTime) THEN BEGIN
  2600.             IUTimePString(newElapsed, kWantSeconds, timeString, gIntlHandle);
  2601.             fFracAppWindow.GetETimeView.SetText(timeString, kRedraw);
  2602.         END;
  2603.  
  2604.         fFracHeader.endingTime := currentTime;
  2605.  
  2606.     END;
  2607.  
  2608. {-------------------------------------------------------------------------------------------}
  2609. {$S ARes}
  2610.  
  2611. PROCEDURE TFracAppDocument.SetMethodName(forceUpdate: Boolean);
  2612.  
  2613. { Sets the string that displays the algorithm being used to create the fractal. Called by
  2614.   TFracAppDocument.DoMakeViews when the document is being created. }
  2615.  
  2616.     VAR
  2617.         methodString:        Str255;
  2618.         stringNumber:        Integer;
  2619.         theString:            Str255;
  2620.  
  2621.     BEGIN
  2622.  
  2623.     { Get the first part of the equation. Our final string will look something like
  2624.       “Using <Calculation Algorithm>/<Offscreen routines>”. We first get the string
  2625.       for “Using ”. }
  2626.  
  2627.         GetIndString(methodString, kAlgorithmStrings, kUsing);
  2628.  
  2629.  
  2630.     { Next, get the name for the algorithm. Convert the version number in
  2631.       fFracHeader into a string index so we can call GetIndString. If we don’t know
  2632.       the version number, punt to a string that says “Unknown.” This should never
  2633.       happen, but it doesn’t hurt to play it safe. When we get the string, add it
  2634.       to the first part. We now have “Using <Calculation Algorithm>”. }
  2635.  
  2636.         CASE fFracHeader.version OF
  2637.             kNormalVersion: stringNumber := kNormalAlgorithm;
  2638.             kFastVersion: stringNumber := kFastAlgorithm;
  2639.             OTHERWISE stringNumber := kUnknownAlgorithm;
  2640.         END;
  2641.         GetIndString(theString, kAlgorithmStrings, stringNumber);
  2642.         methodString := ConCat(methodString, theString);
  2643.  
  2644.  
  2645.     { If we don’t know what algorithm we are using, then we are done. Otherwise, we
  2646.       need to add the string that says what offscreen routines we are using. Get
  2647.       the slash, add it to the string, and then get the string that describes the
  2648.       offscreen routines. }
  2649.  
  2650.         IF stringNumber <> kUnknownAlgorithm THEN BEGIN
  2651.             GetIndString(theString, kAlgorithmStrings, kSlash);
  2652.             methodString := ConCat(methodString, theString);
  2653.             CASE fFracHeader.use32BitCQD OF
  2654.                 TRUE: stringNumber := k32CQDRoutines;
  2655.                 FALSE: stringNumber := kHomebrewRoutines;
  2656.             END;
  2657.             GetIndString(theString, kAlgorithmStrings, stringNumber);
  2658.             methodString := ConCat(methodString, theString);
  2659.         END;
  2660.  
  2661.         fFracAppWindow.GetMethodView.SetText(methodString, kRedraw);
  2662.     END;
  2663.  
  2664. {-------------------------------------------------------------------------------------------}
  2665. {$S ARes}
  2666.  
  2667. PROCEDURE TFracAppDocument.SetVersion(version: Integer);
  2668.  
  2669. { Called by the TFracAppEngine when it initializes itself. The engine is responsible for
  2670.   keeping track of what version document is created. Calling SetVersion is its way of
  2671.   informing the document what version it is. }
  2672.  
  2673.     BEGIN
  2674.         fFracHeader.version := version;
  2675.     END;
  2676.  
  2677. {-------------------------------------------------------------------------------------------}
  2678. {$S ARes}
  2679.  
  2680. PROCEDURE TFracAppDocument.StashSelectedRange;
  2681.  
  2682. { Handy routine that stores information describing the current selection to the global
  2683.   variable ‘gPageRecord’. This is called when creating a new document based on the current
  2684.   selection in another document. }
  2685.  
  2686.     VAR
  2687.         selection:            Rect;
  2688.  
  2689.     BEGIN
  2690.  
  2691.         selection := fFracAppWindow.GetFracAppView.GetSelection;
  2692.  
  2693.     { The following WITH statement is a bit obtuse. Here is prototype statement
  2694.       that translates what is really going on. }
  2695.  
  2696.     { gPageRecord.RealMin := fFracHeader.pages.RealMin + fFracHeader.deltaP * selection.top; }
  2697.  
  2698.         WITH fFracHeader, pages, selection DO BEGIN
  2699.             gPageRecord.RealMin := RealMin + deltaP * top;
  2700.             gPageRecord.ImagMin := ImagMin + deltaQ * left;
  2701.             gPageRecord.RealMax := RealMin + deltaP * bottom;
  2702.             gPageRecord.ImagMax := ImagMin + deltaQ * right;
  2703.         END;
  2704.     END;
  2705.  
  2706. {-------------------------------------------------------------------------------------------}
  2707. {$S AFields}
  2708.  
  2709. PROCEDURE TFracAppDocument.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  2710.                                                       fieldType: Integer)); OVERRIDE;
  2711.  
  2712.     BEGIN
  2713.         DoToField('TFracAppDocument', NIL, bClass);
  2714.         {$Push} {$H-}
  2715.         ShowFracHeader('fFracHeader', fFracHeader, DoToField);
  2716.         {$Pop}
  2717.         DoToField('fFracAppEngine', @fFracAppEngine, bObject);
  2718.         DoToField('fOffWorlder', @fOffWorlder, bObject);
  2719.         DoToField('fFracAppWindow', @fFracAppWindow, bObject);
  2720.         DoToField('fStartupMode', @fStartupMode, bInteger);
  2721.  
  2722.         { Print fields of anscestors }
  2723.         INHERITED Fields(DoToField);
  2724.     END;
  2725.  
  2726. {-------------------------------------------------------------------------------------------}
  2727. {---------------------------------TFracAppWindow Methods------------------------------------}
  2728. {-------------------------------------------------------------------------------------------}
  2729.  
  2730. {$S AOpen}
  2731.  
  2732. PROCEDURE TFracAppWindow.IRes(itsDocument: TDocument; itsSuperView: TView;
  2733.                               VAR itsParams: Ptr); OVERRIDE;
  2734.  
  2735. { Initialize our window object. Overridden to set our instance variables to NIL. }
  2736.  
  2737.     BEGIN
  2738.         fCTimeLabelView := NIL;
  2739.         fCTimeView := NIL;
  2740.         fETimeLabelView := NIL;
  2741.         fETimeView := NIL;
  2742.         fFracAppView := NIL;
  2743.         fMethodNameView := NIL;
  2744.         fPercentView := NIL;
  2745.         fPercentLabelView := NIL;
  2746.         fSingleBarView := NIL;
  2747.  
  2748.         INHERITED IRes(itsDocument, itsSuperView, itsParams);
  2749.  
  2750.     END;
  2751.  
  2752. {-------------------------------------------------------------------------------------------}
  2753. {$S ANonRes}
  2754.  
  2755. PROCEDURE TFracAppWindow.AdjustInfoBar(width, height: VCoordinate; invalidate: Boolean);
  2756.  
  2757. { Called by TFracAppWindow.Show and Resize to make sure the items in the infobar and the
  2758.   percentage complete label are all in the right places. }
  2759.  
  2760.     CONST
  2761.         kMaxPctString        = '000';            { String representing the largest string that will be in the Percent Completed field. }
  2762.         kInfoBarMargin        = 5;                { Margin to use on the left and right sides of the infobar. }
  2763.         kLabelMargin        = 5;                { Margin to use between a time display and its label. }
  2764.         kMinimumSpacing     = 15;                { Minimum space to maintain between fields. }
  2765.         kTimeSlop            = 0;                { Slop to add to time elements. }
  2766.         kStaticTextSlop     = 1;                { Slop to add in GetTextWidth. The width of our
  2767.         TStaticText items can’t just be the width of the string. We also have to add a 1 pixel
  2768.         slop. This is because imaging is done with TextEdit, which inserts a 1 pixel margin
  2769.         at the left so that it can put an insertion point there. We have to take this into account
  2770.         when sizing our fields. }
  2771.  
  2772.     TYPE
  2773.  
  2774.     { We define this record to keep track of the new locations of our fields. Each
  2775.       one needs a newLocation specification, as well as a horizontal size (we never
  2776.       change an item’s height). We also keep a newEnd field here, which is
  2777.       newLocation.h + newSize, for convenience. }
  2778.  
  2779.         NewSpecs            = RECORD
  2780.             newLocation:        Point;
  2781.             newEnd:             Integer;
  2782.             newSize:            Integer;
  2783.             END;
  2784.  
  2785.     VAR
  2786.         itsText:            Str255;
  2787.         itsStyle:            TextStyle;
  2788.         specCTimeLabel:     NewSpecs;
  2789.         specCTime:            NewSpecs;
  2790.         specETimeLabel:     NewSpecs;
  2791.         specETime:            NewSpecs;
  2792.         specMethodName:     NewSpecs;
  2793.         specPercentLabel:    NewSpecs;
  2794.         specPercent:        NewSpecs;
  2795.         specSingleBar:        NewSpecs;
  2796.         rightEdge:            Integer;
  2797.         topEdge:            Integer;
  2798.         bottomScrollbar:    TScrollbar;
  2799.  
  2800.     FUNCTION GetTextWidth(itsText: Str255; itsTextStyle: TextStyle): Integer;
  2801.  
  2802.     { Handy subroutine that takes a string and a style that it will be imaged with.
  2803.       It returns the width of that string in that style, adding in kStaticTextSlop. }
  2804.  
  2805.         VAR
  2806.             oldPort:            GrafPtr;
  2807.  
  2808.         BEGIN
  2809.             GetPort(oldPort);
  2810.             SetPort(gWorkPort);
  2811.             SetPortTextStyle(itsTextStyle);
  2812.             GetTextWidth := StringWidth(itsText) + kStaticTextSlop;
  2813.             SetPort(oldPort);
  2814.         END;
  2815.  
  2816.     PROCEDURE MoveView(theView: TView; theSpecs: NewSpecs);
  2817.  
  2818.     { Subroutine that moves and sizes “theView” according to “theSpecs” }
  2819.  
  2820.         BEGIN
  2821.             WITH theView, theSpecs DO BEGIN
  2822.                 Locate(newLocation.h, newLocation.v, invalidate);
  2823.                 Resize(newSize, fSize.v, invalidate);
  2824.             END;
  2825.         END;
  2826.  
  2827.     BEGIN
  2828.  
  2829.     { We don’t want to do anything if none of our subviews exists yet. It is
  2830.       possible for this routine to get called before the window is completely
  2831.       built, in which case our subviews might not be created yet. Check to see if
  2832.       they exists, and exit if they don’t. }
  2833.  
  2834.         IF (fCTimeLabelView = NIL) THEN
  2835.             EXIT(AdjustInfoBar);
  2836.  
  2837.     { Do the leftmost item. This is the fields that says “Calculation Time:
  2838.       xx/xx/xx”. It is in two parts: a TStaticText for the “Calculation Time: ”
  2839.       (the label) and another for the time itself. The label is positioned
  2840.       kInfoBarMargin pixels from the left. The time field is positioned
  2841.       kLabelMargin pixels past the label. }
  2842.  
  2843.         fCTimeLabelView.GetText(itsText);
  2844.         itsStyle := fCTimeLabelView.fTextStyle;
  2845.         WITH specCTimeLabel DO BEGIN
  2846.             newSize := GetTextWidth(itsText, itsStyle) + kLabelMargin;
  2847.             newLocation.h := kInfoBarMargin;
  2848.             newLocation.v := fCTimeLabelView.fLocation.v;
  2849.             newEnd := newLocation.h + newSize;
  2850.         END;
  2851.  
  2852.         itsText := gMaxWidthTimeString;
  2853.         itsStyle := fCTimeView.fTextStyle;
  2854.         WITH specCTime DO BEGIN
  2855.             newSize := GetTextWidth(itsText, itsStyle) + kTimeSlop;
  2856.             newLocation.h := specCTimeLabel.newEnd;
  2857.             newLocation.v := fCTimeView.fLocation.v;
  2858.             newEnd := newLocation.h + newSize;
  2859.         END;
  2860.  
  2861.     { Do the rightmost item. This is the fields that says “Elapsed Time: xx/xx/xx”.
  2862.       It is in two parts: a TStaticText for the “Elapsed Time: ” (the label) and
  2863.       another for the time itself.The time field is positioned kInfoBarMargin
  2864.       pixels from the right edge of the window. The label is positioned
  2865.       kLabelMargin pixels to the right of the time field. }
  2866.  
  2867.         WITH specETime DO BEGIN
  2868.             newSize := specCTime.newSize;
  2869.             newEnd := width - kInfoBarMargin;
  2870.             newLocation.h := newEnd - newSize;
  2871.             newLocation.v := fETimeView.fLocation.v;
  2872.         END;
  2873.  
  2874.         fETimeLabelView.GetText(itsText);
  2875.         itsStyle := fETimeLabelView.fTextStyle;
  2876.         WITH specETimeLabel DO BEGIN
  2877.             newSize := GetTextWidth(itsText, itsStyle) + kLabelMargin;
  2878.             newEnd := specETime.newLocation.h;
  2879.             newLocation.h := newEnd - newSize;
  2880.             newLocation.v := fETimeLabelView.fLocation.v;
  2881.         END;
  2882.  
  2883.     { Center the middle item. This is the TStaticText item that holds the string
  2884.       displaying what algorithms we are using. Its position is determined by
  2885.       centering it between the  left and right edges of the window. }
  2886.  
  2887.         fMethodNameView.GetText(itsText);
  2888.         itsStyle := fMethodNameView.fTextStyle;
  2889.         WITH specMethodName DO BEGIN
  2890.             newSize := GetTextWidth(itsText, itsStyle);
  2891.             newLocation.h := (width - newSize) DIV 2;
  2892.             newLocation.v := fMethodNameView.fLocation.v;
  2893.             newEnd := newLocation.h + newSize;
  2894.         END;
  2895.  
  2896.     { Make sure we maintain minimum spacing. Examine the locations of the three
  2897.       items we’ve just calculated. See if they are all at least kMinimumSpacing
  2898.       pixels from each other. If not, then shift everything over to the right to
  2899.       maintain this spacing. Note that this means that the Elapsed time field can
  2900.       get shoved off the right edge of the window if the window is too small. }
  2901.  
  2902.         specMethodName.newLocation.h := MAX(specMethodName.newLocation.h, specCTime.newEnd +
  2903.                                             kMinimumSpacing);
  2904.         specETimeLabel.newLocation.h := MAX(specETimeLabel.newLocation.h,
  2905.                                             specMethodName.newLocation.h +
  2906.                                             specMethodName.newSize + kMinimumSpacing);
  2907.         specETime.newLocation.h := specETimeLabel.newLocation.h + specETimeLabel.newSize;
  2908.  
  2909.     { Do the percent complete item at the bottom. This guy is centered between the
  2910.       left edge of the window, and the right edge of the bottom scrollbar. Note
  2911.       that this is the only item here that I move around vertically as well. Moving
  2912.       the Percent Complete item also means having to move the little line used to
  2913.       seperate it from the fractal view above it. }
  2914.  
  2915.         bottomScrollbar := fFracAppView.GetScroller(FALSE).fScrollbars[h];
  2916.         rightEdge := bottomScrollbar.fLocation.h;
  2917.         topEdge := height - bottomScrollbar.fSize.v;
  2918.  
  2919.         WITH specSingleBar DO BEGIN
  2920.             newSize := rightEdge;
  2921.             newLocation.h := 0;
  2922.             newLocation.v := topEdge + 1;
  2923.         END;
  2924.  
  2925.         fPercentLabelView.GetText(itsText);
  2926.         itsStyle := fPercentLabelView.fTextStyle;
  2927.         specPercentLabel.newSize := GetTextWidth(itsText, itsStyle);
  2928.  
  2929.         itsText := kMaxPctString;
  2930.         itsStyle := fPercentView.fTextStyle;
  2931.         WITH specPercent DO BEGIN
  2932.             newSize := GetTextWidth(itsText, itsStyle);
  2933.             newLocation.h := (rightEdge - newSize - specPercentLabel.newSize) DIV 2;
  2934.             newLocation.v := topEdge + 2;
  2935.             newEnd := newLocation.h + newSize;
  2936.         END;
  2937.  
  2938.         WITH specPercentLabel DO BEGIN
  2939.             newLocation.h := specPercent.newEnd;
  2940.             newLocation.v := topEdge + 2;
  2941.         END;
  2942.  
  2943.     {--- Move them all into position ---}
  2944.  
  2945.         MoveView(fCTimeLabelView, specCTimeLabel);
  2946.         MoveView(fCTimeView, specCTime);
  2947.         MoveView(fETimeLabelView, specETimeLabel);
  2948.         MoveView(fETimeView, specETime);
  2949.         MoveView(fMethodNameView, specMethodName);
  2950.         MoveView(fPercentLabelView, specPercentLabel);
  2951.         MoveView(fPercentView, specPercent);
  2952.         MoveView(fSingleBarView, specSingleBar);
  2953.  
  2954.     END;
  2955.  
  2956. {-------------------------------------------------------------------------------------------}
  2957. {$S ARes}
  2958.  
  2959. FUNCTION TFracAppWindow.GetCTimeView: TStaticText;
  2960.  
  2961.     BEGIN
  2962.         GetCTimeView := fCTimeView;
  2963.     END;
  2964.  
  2965. {-------------------------------------------------------------------------------------------}
  2966. {$S ARes}
  2967.  
  2968. FUNCTION TFracAppWindow.GetETimeView: TStaticText;
  2969.  
  2970.     BEGIN
  2971.         GetETimeView := fETimeView;
  2972.     END;
  2973.  
  2974. {-------------------------------------------------------------------------------------------}
  2975. {$S ARes}
  2976.  
  2977. FUNCTION TFracAppWindow.GetFracAppView: TFracAppView;
  2978.  
  2979.     BEGIN
  2980.         GetFracAppView := fFracAppView;
  2981.     END;
  2982.  
  2983. {-------------------------------------------------------------------------------------------}
  2984. {$S ARes}
  2985.  
  2986. FUNCTION TFracAppWindow.GetMethodView: TStaticText;
  2987.  
  2988.     BEGIN
  2989.         GetMethodView := fMethodNameView;
  2990.     END;
  2991.  
  2992. {-------------------------------------------------------------------------------------------}
  2993. {$S ARes}
  2994.  
  2995. FUNCTION TFracAppWindow.GetPercentView: TStaticText;
  2996.  
  2997.     BEGIN
  2998.         GetPercentView := fPercentView;
  2999.     END;
  3000.  
  3001. {-------------------------------------------------------------------------------------------}
  3002. {$S AOpen}
  3003.  
  3004. PROCEDURE TFracAppWindow.Open; OVERRIDE;
  3005.  
  3006. { Called by MacApp when it needs to open a window. We override it here so that we can also
  3007.   call AdjustInfoBar. }
  3008.  
  3009.     BEGIN
  3010.         SELF.AdjustInfoBar(fSize.h, fSize.v, kDontInvalidate);
  3011.         INHERITED Open;
  3012.     END;
  3013.  
  3014. {-------------------------------------------------------------------------------------------}
  3015. {$S ANonRes}
  3016.  
  3017. PROCEDURE TFracAppWindow.Resize(width, height: VCoordinate; invalidate: Boolean); OVERRIDE;
  3018.  
  3019. { Called by MacApp when it needs to resize a window. We override it here so that we can also
  3020.   call AdjustInfoBar. }
  3021.  
  3022.     BEGIN
  3023.         INHERITED Resize(width, height, invalidate);
  3024.         SELF.AdjustInfoBar(width, height, invalidate);
  3025.     END;
  3026.  
  3027. {-------------------------------------------------------------------------------------------}
  3028. {$S ANonRes}
  3029.  
  3030. PROCEDURE TFracAppWindow.Show(state, redraw: Boolean); OVERRIDE;
  3031.  
  3032. { We support a Windows menu by having smarter than average windows. These windows tell
  3033.   MacApp when they are showing and hiding themselves. They do this by calling
  3034.   TFracAppApplication.InstallWindowMenuItem. That method notes what is going on, and
  3035.   rebuilds the Windows menu appropriately. }
  3036.  
  3037.     BEGIN
  3038.         INHERITED Show(state, redraw);
  3039.         TFracAppApplication(gApplication).InstallWindowMenuItem(SELF, state);
  3040.     END;
  3041.  
  3042. {-------------------------------------------------------------------------------------------}
  3043. {$S ARes}
  3044.  
  3045. PROCEDURE TFracAppWindow.SetTitle(newTitle: Str255); OVERRIDE;
  3046.  
  3047. { If our window changes its name, we need to tell the application so that it can rebuild the
  3048.   Windows menu. Otherwise, we’ll have a menu item that refers to a window whose title no
  3049.   longer matches that of the menu item. }
  3050.  
  3051.     BEGIN
  3052.         INHERITED SetTitle(newTitle);
  3053.         InvalidateMenus;
  3054.         gRebuildWindowsMenu := TRUE;
  3055.     END;
  3056.  
  3057. {-------------------------------------------------------------------------------------------}
  3058. {$S AFields}
  3059.  
  3060. PROCEDURE TFracAppWindow.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  3061.                                                     fieldType: Integer)); OVERRIDE;
  3062.  
  3063.     BEGIN
  3064.         DoToField('TFracAppWindow', NIL, bClass);
  3065.         DoToField('fCTimeLabelView', @fCTimeLabelView, bObject);
  3066.         DoToField('fCTimeView', @fCTimeView, bObject);
  3067.         DoToField('fETimeLabelView', @fETimeLabelView, bObject);
  3068.         DoToField('fETimeView', @fETimeView, bObject);
  3069.         DoToField('fFracAppView', @fFracAppView, bObject);
  3070.         DoToField('fMethodNameView', @fMethodNameView, bObject);
  3071.         DoToField('fPercentLabelView', @fPercentLabelView, bObject);
  3072.         DoToField('fPercentView', @fPercentView, bObject);
  3073.         DoToField('fSingleBarView', @fSingleBarView, bObject);
  3074.  
  3075.         { Print fields of anscestors }
  3076.         INHERITED Fields(DoToField);
  3077.     END;
  3078.  
  3079. {-------------------------------------------------------------------------------------------}
  3080. {-------------------------------TNoFlashStaticText Methods----------------------------------}
  3081. {-------------------------------------------------------------------------------------------}
  3082. { This object is used to display the elapsed time items, and the percentage complete item. I
  3083.   found that with a normal TStaticText item, I got too much flashing when I updated the
  3084.   strings. There was too much of a time gap between when the old string got erased, and the
  3085.   new one got drawn. I’ve tried to minimize that by having MATextbox (which is what
  3086.   ultimately draws the string) erase the old text when it draws the new. There is still SOME
  3087.   flicker, but not as much as there used to be. }
  3088. {-------------------------------------------------------------------------------------------}
  3089. {$S ARes}
  3090.  
  3091. PROCEDURE TNoFlashStaticText.ImageText(text: Ptr; LENGTH: LONGINT; box: Rect;
  3092.     just: Integer); OVERRIDE;
  3093.  
  3094. { Called by TStaticText in its Draw method to do the actual imaging of text. Overridden so
  3095.   that it passes the kEraseText parameter to MATextbox. }
  3096.  
  3097.     BEGIN
  3098.         MATextBox(text, LENGTH, box, just, fAutoWrap, NIL, kEraseFirst, kSpaceForCaret);
  3099.     END;
  3100.  
  3101. {-------------------------------------------------------------------------------------------}
  3102. {$S ARes}
  3103.  
  3104. PROCEDURE TNoFlashStaticText.SetText(theText: Str255; redraw: Boolean); OVERRIDE;
  3105.  
  3106. { This method is called to set the text that the TStaticText item displays. It draws the
  3107.   text immediately, without invalidating the screen and simply responding to the update
  3108.   event. In doing this, it calls EraseRect, and then calls Draw. Overridden to inhibit
  3109.   erasing the old text before drawing the new, as we now do that in ImageText. }
  3110.  
  3111.     VAR
  3112.         area:                Rect;
  3113.  
  3114.     BEGIN
  3115.         IF (fDataHandle = NIL) | (theText <> fDataHandle^^) THEN BEGIN
  3116.             SELF.ReleaseText;
  3117.             fDataHandle := NewString(theText);
  3118.             IF MemError <> noErr THEN
  3119.                 fDataHandle := NIL;
  3120.             IF redraw & SELF.Focus & SELF.IsVisible THEN BEGIN
  3121.                 SELF.ControlArea(area);
  3122.                 { EraseRect(area); }                { Removed from original code. }
  3123.                 SELF.Draw(area);
  3124.             END;
  3125.         END;
  3126.     END;
  3127.  
  3128. {-------------------------------------------------------------------------------------------}
  3129. {$S AFields}
  3130.  
  3131. PROCEDURE TNoFlashStaticText.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  3132.                                                         fieldType: Integer)); OVERRIDE;
  3133.  
  3134.     BEGIN
  3135.         DoToField('TNoFlashStaticText', NIL, bClass);
  3136.  
  3137.         { Print fields of anscestors }
  3138.         INHERITED Fields(DoToField);
  3139.     END;
  3140.  
  3141. {-------------------------------------------------------------------------------------------}
  3142. {----------------------------------TFracAppView Methods-------------------------------------}
  3143. {-------------------------------------------------------------------------------------------}
  3144. { This is the object that displays the fractal. Besides having its .Draw method overridden
  3145.   so that it can copybits the offscreen image, this View also keeps track of the current
  3146.   selection rectangle. Because of this, it also handles the menu items that would only be
  3147.   enabled when there is a selection. This includes Copy, New From Selection, and New
  3148.   Multi-Page. }
  3149. {-------------------------------------------------------------------------------------------}
  3150. {$S AOpen}
  3151.  
  3152. PROCEDURE TFracAppView.IRes(itsDocument: TDocument; itsSuperView: TView;
  3153.     VAR itsParams: Ptr); OVERRIDE;
  3154.  
  3155. { Init our FracAppView. This sets the current selection to an empty rect, and keeps a
  3156.   reference to our owning document. This is so we can call some methods in that document
  3157.   that return information about the picture we have to display. This reference is kept in
  3158.   addition to the fDocument field that TViews already have. Our own reference is stored as
  3159.   a TFracAppDocument, and not just a plain TDocument. This is because we need a reference to
  3160.   a TFracAppDocument in order to access the methods that it has that a normal TDocument
  3161.   doesn’t. We could just coerce fDocument into a TFracAppDocument whenever we accessed it,
  3162.   but that’s a pain, and this is inexpensive. }
  3163.  
  3164.     BEGIN
  3165.         fSelectionRect := gZeroRect;
  3166.         fFracAppDocument := TFracAppDocument(itsDocument);
  3167.  
  3168.         INHERITED IRes(itsDocument, itsSuperView, itsParams);
  3169.     END;
  3170.  
  3171. {-------------------------------------------------------------------------------------------}
  3172. {$S ARes}
  3173.  
  3174. PROCEDURE TFracAppView.DoHighlightSelection(fromHL, toHL: HLState); OVERRIDE;
  3175.  
  3176. { Called by MacApp’s updating routines to highlight the current selection rectangle if there
  3177.   is one. This is drawn in srcCopy mode to make it stand out better when it is a final
  3178.   selection.  XOR is used for the rubberband, until mouseUp. }
  3179.  
  3180.     VAR
  3181.         selPatHandle:        PatHandle;
  3182.         tempRect:            Rect;
  3183.  
  3184.     BEGIN
  3185.         IF SELF.HasSelection THEN BEGIN
  3186.             tempRect := SELF.GetSelection;
  3187.             IF (toHL = hlOn) THEN BEGIN
  3188.  
  3189.                 selPatHandle := GetPattern(kSelPattern); { get the pattern we use. }
  3190.                 IF selPatHandle <> NIL THEN         { If pattern available, use it. }
  3191.                     PenPat(selPatHandle^^);         { set pen pattern to our selection kind.
  3192.                                                      }
  3193.                 PenMode(srcCopy);                    { copy mode on pattern selection. }
  3194.                 FrameRect(tempRect);                { outline the frame of selection. }
  3195.             END                                     { highlight turned on. }
  3196.             ELSE BEGIN
  3197.  
  3198.             { Turning off the highlight. We need to remove the traces of the selection.
  3199.               To do this, redraw that rectangle. }
  3200.  
  3201.                 SELF.Draw(tempRect);                { Redraw it to clear selection. }
  3202.             END;
  3203.         END;
  3204.     END;                                            { TFracAppView.DoHighlightSelection }
  3205.  
  3206. {-------------------------------------------------------------------------------------------}
  3207. {$S ADoCommand}
  3208.  
  3209. FUNCTION TFracAppView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
  3210.  
  3211. { Called by MacApp when a menu item has been chosen. Handle the menu choices for “New From
  3212.   Selection” and “New MultiPage…” out of the File Menu. These make new Fractals based on the
  3213.   current selection. It does it by calling on the application object to make a new document.
  3214.   The communication to the DoInitialState is through the global variable gPageRecord. }
  3215.  
  3216.     VAR
  3217.         pageDialog:         TWindow;                { input on multiple pages. }
  3218.         dismisser:            IDType;                    { Button pressed in multiPage Dialog }
  3219.         horPages:            LONGINT;                { Horizontal number of pages to make }
  3220.         verPages:            LONGINT;                { Vertical number of pages to make }
  3221.         qdExtent:            Rect;                    { For Select All }
  3222.  
  3223.     BEGIN
  3224.  
  3225.     { Assume that we have no command to return, since none of our commands currently
  3226.       change the document. }
  3227.  
  3228.         DoMenuCommand := gNoChanges;
  3229.  
  3230.         CASE aCmdNumber OF
  3231.  
  3232.             cCopy: BEGIN
  3233.                 FailOSErr(ZeroScrap);
  3234.                 SELF.WriteToDeskScrap;
  3235.                 gApplication.CheckDeskScrap;        { Force MacApp to notice the change }
  3236.             END;
  3237.  
  3238.             cSelectAll: BEGIN
  3239.                 SELF.GetQDExtent(qdExtent);
  3240.                 SELF.SetSelection(qdExtent, kRedraw)
  3241.             END;
  3242.  
  3243.             cNewFromSelection: BEGIN
  3244.                 fFracAppDocument.StashSelectedRange;
  3245.                 gApplication.OpenNew(cNewFromSelection);
  3246.             END;
  3247.  
  3248.             cNewMultiPage: BEGIN
  3249.  
  3250.             { When they choose the MultiPage option, we have to put up the dialog to find
  3251.               out what they want to do, get the values back, store them into the
  3252.               gPageRecord global, and start up the first document based on the current
  3253.               selection and the number of pages to do. }
  3254.  
  3255.             { Run the dialog to get the number of pages desired. }
  3256.  
  3257.                 pageDialog := TWindow(NewTemplateWindow(kMultiDialog, NIL));
  3258.                 dismisser := TDialogView(pageDialog.FindSubView('DLOG')).PoseModally;
  3259.                 horPages := TNumberText(pageDialog.FindSubView('HORZ')).GetValue;
  3260.                 verPages := TNumberText(pageDialog.FindSubView('VERT')).GetValue;
  3261.                 pageDialog.Close;
  3262.  
  3263.                 IF dismisser = 'OKOK' THEN BEGIN
  3264.  
  3265.                     fFracAppDocument.StashSelectedRange;
  3266.  
  3267.                 { Now that we have a extended rectangle defining the area to calculate, we
  3268.                   need to get the next document’s extended rectangle by dividing that large
  3269.                   rectangle by the number of pages desired. }
  3270.  
  3271.                     WITH gPageRecord DO BEGIN
  3272.                         RealMax := RealMin + (RealMax - RealMin) / verPages;
  3273.                         ImagMax := ImagMin + (ImagMax - ImagMin) / horPages;
  3274.                     END;
  3275.  
  3276.                { Now we have the page count desired, save it off in the global gPageRecord. }
  3277.  
  3278.                     WITH gPageRecord DO BEGIN
  3279.                         maxH := horPages;
  3280.                         maxV := verPages;
  3281.                         currentH := 1;                { Start at page 1 on both axes. }
  3282.                         currentV := 1;
  3283.                     END;
  3284.  
  3285.                 { Now we have the actual area to use as the rectangle to calculate, we need to
  3286.                   turn the real numbers into the page rectangle that we will use to print and
  3287.                   save and so on. This is done by creating the full document in DoMakeDocument,
  3288.                   and the global variables set here will be used there. }
  3289.  
  3290.                     gApplication.OpenNew(cNewMultiPage);
  3291.                 END;                                { IF dismisser }
  3292.             END;
  3293.  
  3294.             OTHERWISE DoMenuCommand := INHERITED DoMenuCommand(aCmdNumber); { next guy in
  3295.                 chain. }
  3296.         END;                                        { CASE on aCmdNumber }
  3297.  
  3298.     END;                                            { TFracAppView.DoMenuCommand }
  3299.  
  3300. {-------------------------------------------------------------------------------------------}
  3301. {$S ASelCommand}
  3302.  
  3303. FUNCTION TFracAppView.DoMouseCommand(VAR theMouse: Point; VAR info: EventInfo;
  3304.                                      VAR hysteresis: Point): TCommand; OVERRIDE;
  3305.  
  3306. { Called by MacApp to handle the mouse events in the view. This will pass back the command
  3307.   object to handle tracking the mouse and creating a new selection in preparation for making
  3308.   a new fractal. }
  3309.  
  3310.     VAR
  3311.         tracker:            TAreaSelector;
  3312.  
  3313.     BEGIN
  3314.         New(tracker);                                { make a new command object. }
  3315.         FailNil(tracker);                            { no memory, trash out. }
  3316.         tracker.IAreaSelector(SELF, fFracAppDocument); { Initialize the command object. }
  3317.         DoMouseCommand := tracker;                    { return it for later use. }
  3318.     END;                                            { TFracAppView.DoMouseCommand }
  3319.  
  3320. {-------------------------------------------------------------------------------------------}
  3321. {$S ARes}
  3322.  
  3323. PROCEDURE TFracAppView.DoSetupMenus; OVERRIDE;
  3324.  
  3325. { Called by MacApp when the user clicks on the menu. Set up the New Fractal menus choice in
  3326.   Fractal Menu, based on selection. }
  3327.  
  3328.     BEGIN
  3329.         INHERITED DoSetupMenus;                     { Do mainline stuff first. }
  3330.  
  3331.     { If we have a non-zero selection, we can enable the menu item to use it as the
  3332.       new fractal dimensions for this document. }
  3333.  
  3334.         Enable(cCopy, SELF.HasSelection);
  3335.         Enable(cSelectAll, TRUE);
  3336.         Enable(cNewFromSelection, SELF.HasSelection);
  3337.         Enable(cNewMultiPage, SELF.HasSelection);
  3338.     END;                                            { TFracAppView.DoSetupMenus }
  3339.  
  3340. {-------------------------------------------------------------------------------------------}
  3341. {$S ARes}
  3342.  
  3343. PROCEDURE TFracAppView.Draw(area: Rect); OVERRIDE;
  3344.  
  3345. { Called by MacApp to draw the view seen in the window. Every nonblank view MUST override
  3346.   this method. This is the display routine to take the data out of the offscreen buffer and
  3347.   whip it up to the window, as the current view. The fractal is full page size, clips
  3348.   without scaling into the window. }
  3349.  
  3350.     VAR
  3351.         destPort:            GrafPtr;
  3352.         theDevice:            GDHandle;
  3353.  
  3354.     BEGIN
  3355.  
  3356.     { Lock down our bits so that they don’t move when we try to copy them to the
  3357.       screen. }
  3358.  
  3359.         fFracAppDocument.LockThePixels(kLock);
  3360.  
  3361.     { Set the fore and background colors. If we just leave them set to any old
  3362.       values, Copybits will attempt to “colorize” the bitmap that we transfer. See
  3363.       technote #163 for the gory details. }
  3364.  
  3365.         RGBForeColor(gRGBBlack);
  3366.         RGBBackColor(gRGBWhite);
  3367.  
  3368.     { Copy the bits to the screen, allowing CopyBits to sort out the colors. }
  3369.  
  3370.         CopyBits(GrafPtr(fFracAppDocument.GetFracPort)^.portBits, thePort^.portBits, area,
  3371.                  area, srcCopy, NIL);
  3372.  
  3373.     { We’re done with the pixels, so let ‘em float. }
  3374.  
  3375.         fFracAppDocument.LockThePixels(kUnlock);
  3376.     END;                                            { TFracAppView.Draw }
  3377.  
  3378. {-------------------------------------------------------------------------------------------}
  3379. {$S ARes}
  3380.  
  3381. PROCEDURE TFracAppView.GetQDExtent(VAR qdExtent: Rect); OVERRIDE;
  3382.  
  3383. { Overridden so that we can redefine the extent of the view when pasting to the scrap. One
  3384.   of the things that GetQDExtent is used for is by TView.WriteToDeskScrap. When this method
  3385.   is called, it is so that it will image the view into a PICT, and then put this PICT on the
  3386.   desk scrap. GetQDExtent is called to find out the boundaries of this PICT. We special case
  3387.   GetQDExtent here for when we are drawing to that PICT. We know this by the setting of the
  3388.   global variable, gDrawingPICTScrap. In that case, we only want to draw the current
  3389.   selection to the scrap. So we just return the fSelectionRect. }
  3390.  
  3391.     BEGIN
  3392.         IF NOT gDrawingPICTScrap THEN
  3393.             INHERITED GetQDExtent(qdExtent)
  3394.         ELSE
  3395.             qdExtent := SELF.GetSelection;
  3396.     END;
  3397.  
  3398. {-------------------------------------------------------------------------------------------}
  3399. {$S ARes}
  3400.  
  3401. FUNCTION TFracAppView.GetSelection: Rect;
  3402.  
  3403. { Returns the current selection rectangle. Among other routines, this is called by
  3404.   TFracAppDocument.StashSelectedRange to get the information it needs to to help create a
  3405.   new document based on the current selection. }
  3406.  
  3407.     BEGIN
  3408.         GetSelection := fSelectionRect;
  3409.     END;
  3410.  
  3411. {-------------------------------------------------------------------------------------------}
  3412. {$S ARes}
  3413.  
  3414. FUNCTION TFracAppView.HasSelection: Boolean;
  3415.  
  3416. { Returns TRUE if there is a selection on the screen. Handy when we are enabling menu items
  3417.   based on whether or not there is a selection. }
  3418.  
  3419.     BEGIN
  3420.         {$Push} {$H-}
  3421.         HasSelection := NOT EmptyRect(fSelectionRect);
  3422.         {$Pop}
  3423.     END;
  3424.  
  3425. {-------------------------------------------------------------------------------------------}
  3426. {$S ARes}
  3427.  
  3428. PROCEDURE TFracAppView.InvalidRect(r: Rect); OVERRIDE;
  3429.  
  3430. { InvalidRect is a TView method that invalidates the specified part of the view. We override
  3431.   it here to add a little optimization. The ToolBox routine InvalRect does not optimize for
  3432.   when the passed rectangle is empty. Neither does MacApp. So we do it here. }
  3433.  
  3434.     BEGIN
  3435.         IF SELF.Focus THEN BEGIN
  3436.             VisibleRect(r);
  3437.             IF NOT EmptyRect(r) THEN
  3438.                 InvalRect(r);
  3439.         END;
  3440.     END;
  3441.  
  3442. {-------------------------------------------------------------------------------------------}
  3443. {$S ARes}
  3444.  
  3445. PROCEDURE TFracAppView.SetSelection(theSelectionRect: Rect; redraw: Boolean);
  3446.  
  3447. { Sets and redraws the selection. Redraws the selection only if the redraw parameter is
  3448.   TRUE, we can focus (which just means that we have a port to draw into), and if any part of
  3449.   our view is visible. If any of those assertions are FALSE, then we just remember the
  3450.   selection in fSelectionRect. }
  3451.  
  3452.     BEGIN
  3453.         IF redraw & SELF.Focus & SELF.IsVisible THEN BEGIN
  3454.             SELF.DoHighlightSelection(hlOn, hlOff);
  3455.             fSelectionRect := theSelectionRect;
  3456.             SELF.DoHighlightSelection(hlOff, hlOn);
  3457.         END
  3458.         ELSE
  3459.             fSelectionRect := theSelectionRect;
  3460.     END;
  3461.  
  3462. {-------------------------------------------------------------------------------------------}
  3463. {$S AFields}
  3464.  
  3465. PROCEDURE TFracAppView.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  3466.                                                   fieldType: Integer)); OVERRIDE;
  3467.  
  3468.     BEGIN
  3469.         DoToField('TFracAppView', NIL, bClass);
  3470.         DoToField('fFracAppDocument', @fFracAppDocument, bObject);
  3471.         DoToField('fSelectionRect', @fSelectionRect, bRect);
  3472.  
  3473.         { Print fields of anscestors }
  3474.         INHERITED Fields(DoToField);
  3475.     END;
  3476.  
  3477. {-------------------------------------------------------------------------------------------}
  3478. {---------------------------------TFracAppEngine Methods------------------------------------}
  3479. {-------------------------------------------------------------------------------------------}
  3480.     {$S AOpen}
  3481.  
  3482. PROCEDURE TFracAppEngine.IFracAppEngine(itsDocument: TFracAppDocument);
  3483.  
  3484. { Inits the engine object itself. Remembers a reference to its document, and makes a working
  3485.   copy of its FracHeader. This is done for speed so that we don’t have to continually query
  3486.   the document for its info. }
  3487.  
  3488.     BEGIN
  3489.         SELF.IObject;
  3490.  
  3491.         fFracAppDocument := itsDocument;
  3492.         fFracHeaderCopy := fFracAppDocument.GetFracHeader;
  3493.     END;                                            { TFracAppEngine.IFracAppEngine }
  3494.  
  3495. {-------------------------------------------------------------------------------------------}
  3496. {$S ARes}
  3497.  
  3498. FUNCTION TFracAppEngine.CalcCity: Boolean;
  3499.  
  3500. { Abstract method for TFracAppEngine. Called by TFracAppDocument.CalcTown. It is meant to be
  3501.   overridden. It is only here only so that we can refer to a TNormalFracAppEngine or
  3502.   TFastFracAppEngine as a TFracAppEngine, and still call its CalcCity method. Object Pascal
  3503.   makes sure that the right one gets called. }
  3504.  
  3505.     BEGIN
  3506.         IF qDebug THEN BEGIN
  3507.             ProgramBreak('TFracAppDocument.CalcCity: Override me');
  3508.             CalcCity := TRUE;                        { Signal that we’re done, so that we don’t get called again. }
  3509.         END;
  3510.     END;                                            { TFracAppEngine.CalcCity }
  3511.  
  3512. {-------------------------------------------------------------------------------------------}
  3513. {$S AReadFile}
  3514.  
  3515. PROCEDURE TFracAppEngine.DoRead(aRefNum: Integer; rsrcExists, forPrinting: Boolean);
  3516.  
  3517. { Read any data we may have written to disk. We didn’t (see DoWrite below), so we don’t have
  3518.   much to do in order to not read that back in. }
  3519.  
  3520.     BEGIN
  3521.     END;
  3522.  
  3523. {-------------------------------------------------------------------------------------------}
  3524. {$S AWriteFile}
  3525.  
  3526. PROCEDURE TFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean);
  3527.  
  3528. { Write out any information that TFracAppEngines keep laying around. We don’t have any
  3529.   data, so we don’t write anything. This method is called by our document when it’s saving
  3530.   itself to disk. }
  3531.  
  3532.     BEGIN
  3533.     END;
  3534.  
  3535. {-------------------------------------------------------------------------------------------}
  3536. {$S ARes}
  3537.  
  3538. PROCEDURE TFracAppEngine.FAGetCPixel(x, y: Integer; VAR itsRGB: RGBColor);
  3539.  
  3540. { Normal Color QuickDraw has an annoying habit of hiding and showing the cursor when calling
  3541.   GetCPixel -- even for an offscreen pixmap. This bug has been fixed in 32-bit color
  3542.   QuickDraw. This method checks to see what system we are running under. If GetCPixel is OK,
  3543.   we call it. Otherwise, we do it by hand. }
  3544.  
  3545.     VAR
  3546.         pixBase:             Ptr;
  3547.         offPort:             CGrafPtr;
  3548.         mode:                 SignedByte;
  3549.         pixel:                 SignedByte;
  3550.         rowBytes:             Longint;
  3551.         pixelPtr:             Ptr;
  3552.  
  3553.     BEGIN
  3554.         IF gConfiguration.has32BitQD THEN
  3555.             GetCPixel(x, y, itsRGB)
  3556.         ELSE BEGIN
  3557.  
  3558.         { Drag. Gotta do it by hand. This means going into the PixMap and getting the
  3559.           pixel by hand. We call the document to get the pointer to the pixmap base. We
  3560.           then calculate the offset into the pixmap where our pixel lays. This is easy
  3561.           as there is a 1 to 1 correspondance between bytes and pixels. Since the
  3562.           pixmap data could be off in 32-bit space somwhere, we have to jump over into
  3563.           32-bit mode in order to make sure we can access our bits. We get the pixel,
  3564.           and then jump right back into whatever mode we were in before. After we have
  3565.           our pixel, we convert it into an RGB value with Index2Color. Because all of
  3566.           this takes up more System calls (GetOffPixBase and GetFracPort make system
  3567.           calls, in addition to 2 SwapMMUModes and and Index2Color) versus a single
  3568.           GetCPixel, doing all of this by hand is slower than letting the system do it. }
  3569.  
  3570.             pixBase := fFracAppDocument.GetOffPixBase;
  3571.             offPort := fFracAppDocument.GetFracPort;
  3572.             rowBytes := BAnd(offPort^.portPixMap^^.rowBytes, $00001FFF);
  3573.             pixelPtr := Ptr(Longint(pixBase) + y*rowbytes + x);
  3574.             mode := true32B;
  3575.             SwapMMUMode(mode);
  3576.             pixel := pixelPtr^;
  3577.             SwapMMUMode(mode);
  3578.             Index2Color(BAnd(pixel,$000000FF), itsRGB);
  3579.         END;
  3580.     END;
  3581.  
  3582. {-------------------------------------------------------------------------------------------}
  3583. { This is the heart and soul of this program. Given a point and some other defining
  3584.   constants, this routine figures out what color the point should be. Basically, it works
  3585.   like this:
  3586.  
  3587.   Mandelbrot fractals are calculated on the complex coordinate plane. This means that
  3588.   complex numbers of the form a + ib are ploted in x,y fashion on a two dimensional grid.
  3589.   The value ‘a’ is plotted in the x, or real, direction, and the value ‘b’ is plotted in the
  3590.   y, or imaginary, direction.
  3591.  
  3592.   Given a point a + ib, we square it and add a complex constant C = Po + iQo. We then check
  3593.   to see how far the result is away from the first point. If it is farther than some limit
  3594.   (called M here), we are done. If the result is within that limit, we apply the formula
  3595.   again to that result. We keep this process up until we either go outside the limit “M”, or
  3596.   we have performed this process a certain number of times (called the dwell limit). It is
  3597.   this number of iterations that determines the color of the pixel. If we exceed our maximum
  3598.   number of iterations, we map the color to black.
  3599.  
  3600.   The actual routine is in Assembly for speed (about 85-90% of program time is spent in this
  3601.   one routine!), but the Pascal representation is shown here.
  3602. {-------------------------------------------------------------------------------------------}
  3603. {$S ARes}
  3604.  
  3605. PROCEDURE TFracAppEngine.GoFigger(x, y, Po, Qo: Extended; M, K: Integer; VAR kol: Integer);
  3606.     EXTERNAL;
  3607.  
  3608. {$IFC FALSE}
  3609. {[f-]}
  3610.  
  3611.     VAR
  3612.         x1, y1               : Extended;
  3613.  
  3614.     BEGIN
  3615.         kol := 0;
  3616.         REPEAT
  3617.  
  3618.         {
  3619.             Given a point: pt = x + iy,
  3620.             and a constant: C = Po + iQo,
  3621.             iteratively calculate pt2 = pt^2 + C
  3622.                                       = (x + iy)^2 + (Po + iQo)
  3623.                                       = (x^2 + 2ixy - y^2) + (Po + iQo)
  3624.                                       = (x^2  - y^2 + Po) + i(2xy + Qo)
  3625.             until pt2 is at least “M” units from the original point.
  3626.             The number of times that it took us to get to this state gets
  3627.             mapped into the color that we use for that point. If we iterated more
  3628.             than a certain maximum, then we cap the color to that maximum.
  3629.         }
  3630.  
  3631.             x1 := x * x - y * y + Po;
  3632.             y1 := 2 * x * y + Qo;
  3633.             x := x1;
  3634.             y := y1;
  3635.  
  3636.             kol := kol + 1;
  3637.  
  3638.         UNTIL (kol > K) | ((x * x + y * y) > M);
  3639.  
  3640.     END;
  3641.     {[f+]}
  3642.     {$ENDC}
  3643.  
  3644. {-------------------------------------------------------------------------------------------}
  3645. {$S ARes}
  3646.  
  3647. PROCEDURE TFracAppEngine.ReportRectCompleted(dirtyRect: Rect);
  3648.  
  3649. { Bottleneck used by CalcCity to inform the document that part of the fractal has been
  3650.   freshly calculated and needs to be copied to the screen. }
  3651.  
  3652.     BEGIN
  3653.         fFracAppDocument.PostDraw;
  3654.         fFracAppDocument.ReportRectCompleted(dirtyRect);
  3655.         fFracAppDocument.PreDraw;
  3656.     END;
  3657.  
  3658. {-------------------------------------------------------------------------------------------}
  3659. {$S ARes}
  3660.  
  3661. PROCEDURE TFracAppEngine.PixWhap(col, row: Integer; VAR itsRect: Rect; VAR itsRGB: RGBColor);
  3662.  
  3663. { Called by the engine’s CalcCity method. This method takes a coordinate, calculates the
  3664.   color that it should be by calling GoFigger, and then plots that point. It returns the
  3665.   bounding rectangle of the point so that the view displaying it can update itself, and it
  3666.   returns the color that was plotted as well. This is so we can make boundary comparisons in
  3667.   the TFastFracAppDocument routines. }
  3668.  
  3669.     CONST
  3670.         M                    = 100;                    { This decides what ‘infinity’ is. If
  3671.                                                      value less than this, then loop. }
  3672.         K                    = kNumColors;            { Dwell time, ie, the number of
  3673.                                                      iterations we make before giving up.
  3674.                                                      This is set to match the ‘clut’ created
  3675.                                                      for us. }
  3676.         kBlackIndex         = 255;                    { entry in our offscreen color table for
  3677.                                                      black. }
  3678.  
  3679.     VAR
  3680.         kol:                Integer;                { color to plot. }
  3681.         Po, Qo:             Extended;                { Our starting point. }
  3682.  
  3683.     BEGIN
  3684.  
  3685.         WITH fFracHeaderCopy DO BEGIN
  3686.             Po := pages.RealMin + row * deltaP;     { next starting point }
  3687.             Qo := pages.ImagMin + col * deltaQ;
  3688.         END;
  3689.  
  3690.         SELF.GoFigger(0, 0, Po, Qo, M, K, kol);            { go hammer out a pixel color index }
  3691.  
  3692.         IF kol > K THEN
  3693.             kol := kBlackIndex;                     { Clip it to black if we’re too high. }
  3694.  
  3695.         Index2Color(kol, itsRGB);                    { find the corresponding color }
  3696.         RGBForeColor(itsRGB);                        { and set it. }
  3697.  
  3698.     { Move to the pixel we calculated for, then draw the pixel in right color. This
  3699.       could be done by setting the bytes in pixel map directly, since we own the
  3700.       PixMap and the buffer. However, since most of the application’s time is spent
  3701.       calculating the pixel color (ie, number of iterations through the loop), the
  3702.       overhead of using QuickDraw is very small, and we don’t get much speed
  3703.       improvement by bypassing it. }
  3704.  
  3705.         WITH itsRect DO BEGIN                        { Do a SetRect by hand - I hate calling the System for this. }
  3706.             top := row;
  3707.             bottom := row + 1;
  3708.             left := col;
  3709.             right := col + 1;
  3710.         END;
  3711.         PaintRect(itsRect);                         { draw pixel in offscreen buffer }
  3712.     END;
  3713.  
  3714. {-------------------------------------------------------------------------------------------}
  3715. {$S AFields}
  3716.  
  3717. PROCEDURE TFracAppEngine.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  3718.                                                     fieldType: Integer)); OVERRIDE;
  3719.  
  3720.     BEGIN
  3721.         DoToField('TFracAppEngine', NIL, bClass);
  3722.         DoToField('fFracAppDocument', @fFracAppDocument, bObject);
  3723.  
  3724.         {$Push} {$H-}
  3725.         ShowFracHeader('fFracHeaderCopy', fFracHeaderCopy, DoToField);
  3726.         {$Pop}
  3727.  
  3728.         { Print fields of anscestors }
  3729.         INHERITED Fields(DoToField);
  3730.     END;
  3731.  
  3732. {-------------------------------------------------------------------------------------------}
  3733. {------------------------------TNormalFracAppEngine Methods---------------------------------}
  3734. {-------------------------------------------------------------------------------------------}
  3735. {$S AOpen}
  3736.  
  3737. PROCEDURE TNormalFracAppEngine.INormalFracAppEngine(itsDocument: TFracAppDocument);
  3738.  
  3739. { Initialize our TNormalFracAppEngine object. This sets the version number in our document’s
  3740.   FracHeader, and initializes the pen to (0,0). }
  3741.  
  3742.     BEGIN
  3743.         SELF.IFracAppEngine(itsDocument);
  3744.         fFracAppDocument.SetVersion(kNormalVersion); { ensure that we have a version 1
  3745.                                                       document }
  3746.         fCurrentLocation := gZeroPt;
  3747.  
  3748.     END;
  3749.  
  3750. {-------------------------------------------------------------------------------------------}
  3751. {$S ARes}
  3752.  
  3753. FUNCTION TNormalFracAppEngine.CalcCity: Boolean; OVERRIDE;
  3754.  
  3755. { The procedure to do the idle time processing for the fractal. It does it one pixel at a
  3756.   time to avoid any hit on performance for other applications.    This is called in response
  3757.   to the DoIdle for the application. }
  3758.  
  3759.     VAR
  3760.         tempRect:            Rect;                    { for updating the screen as we
  3761.                                                      calculate. }
  3762.         doUpdate:            Boolean;                { TRUE if we finish a row on this
  3763.                                                      calculation. }
  3764.         documentIsFinished: Boolean;                { TRUE if we finish a row on this
  3765.                                                      calculation. }
  3766.         anRGB:                RGBColor;
  3767.  
  3768.     BEGIN
  3769.  
  3770.     { Calculate the fractal as we go.  Do next pixel here, based on the state saved
  3771.       in fCurrentLocation. When done, those variables are updated to go to the next
  3772.       location to do. It sets the pixel in the offscreen port to be whatever we
  3773.       calculate it to be. After an entire row has been calculated, the appropriate
  3774.       rectangle on the screen is invalidated. This will cause the screen to get
  3775.       updated the next time through the event loop. }
  3776.  
  3777.         SELF.PixWhap(fCurrentLocation.h, fCurrentLocation.v, tempRect, anRGB);
  3778.  
  3779.     { Now we have changed another point in the document.  We need to mark it as
  3780.       changed so we can save the document. }
  3781.  
  3782.         fFracAppDocument.BumpChangeCount;
  3783.  
  3784.     { Up the counters to the next pixel location to do. }
  3785.  
  3786.         doUpdate := FALSE;                            { Assume we don’t need update. }
  3787.         documentIsFinished := FALSE;                { Assume document is not done. }
  3788.         WITH fFracHeaderCopy DO BEGIN
  3789.             fCurrentLocation.h := fCurrentLocation.h + 1; { up the column count. }
  3790.             IF fCurrentLocation.h >= plotWidth THEN BEGIN { did we run off end of row? }
  3791.                 doUpdate := TRUE;                    { done with row, force update. }
  3792.                 fCurrentLocation.h := 0;            { start on the next row. }
  3793.                 fCurrentLocation.v := fCurrentLocation.v + 1; { and up the counter of next
  3794.                                                                row to do. }
  3795.                 IF fCurrentLocation.v >= plotHeight THEN { Check if we are done, and if so,
  3796.                                                           set the flag to stop calculations.
  3797.                                                           }
  3798.                     documentIsFinished := TRUE;
  3799.             END;                                    { start at next row. }
  3800.         END;
  3801.  
  3802.     { If we finished a row, update that row to the screen. }
  3803.  
  3804.         IF doUpdate THEN BEGIN
  3805.             WITH fFracHeaderCopy DO BEGIN
  3806.                 WITH tempRect DO BEGIN                        { Do a SetRect by hand - I hate calling the System for this. }
  3807.                     left := calcRect.left;
  3808.                     top := fCurrentLocation.v - 1;
  3809.                     right := calcRect.right;
  3810.                     bottom := fCurrentLocation.v;
  3811.                 END;
  3812.             END;
  3813.             SELF.ReportRectCompleted(tempRect);
  3814.         END;
  3815.  
  3816.         CalcCity := documentIsFinished;
  3817.     END;                                            { TNormalFracAppDocument.CalcCity }
  3818.  
  3819. {-------------------------------------------------------------------------------------------}
  3820. {$S AReadFile}
  3821.  
  3822. PROCEDURE TNormalFracAppEngine.DoRead(aRefNum: Integer; rsrcExists,
  3823.     forPrinting: Boolean); OVERRIDE;
  3824.  
  3825. { Routine to read the data from the data fork of the file into our engine. This consists
  3826.   only of fCurrentLocation. This method is called by our TDocument.DoRead. The file mark
  3827.   is already set, so we only have to read what’s there. }
  3828.  
  3829.     VAR
  3830.         recsize:            LONGINT;
  3831.  
  3832.     BEGIN
  3833.         recsize := SizeOf(fCurrentLocation);
  3834.         FailOSErr(FSRead(aRefNum, recsize, @fCurrentLocation));
  3835.     END;
  3836.  
  3837. {-------------------------------------------------------------------------------------------}
  3838. {$S AWriteFile}
  3839.  
  3840. PROCEDURE TNormalFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
  3841.  
  3842. { Write out our state variables to disk: fCurrentLocation. This method is called by
  3843.   TFracAppDocument.DoWrite, and it sets up the file mark for us, so we just need to start
  3844.   writing to whereever we happen to be. }
  3845.  
  3846.     VAR
  3847.         recsize:            LONGINT;
  3848.  
  3849.     BEGIN
  3850.         recsize := SizeOf(fCurrentLocation);
  3851.         FailOSErr(FSWrite(aRefNum, recsize, @fCurrentLocation));
  3852.     END;
  3853.  
  3854. {-------------------------------------------------------------------------------------------}
  3855. {$S AFields}
  3856.  
  3857. PROCEDURE TNormalFracAppEngine.Fields(PROCEDURE
  3858.                                       DoToField(fieldName: Str255; fieldAddr: Ptr;
  3859.                                                 fieldType: Integer)); OVERRIDE;
  3860.  
  3861.     BEGIN
  3862.         DoToField('TNormalFracAppEngine', NIL, bClass);
  3863.         DoToField('fCurrentLocation', @fCurrentLocation, bPoint);
  3864.  
  3865.         { Print fields of anscestors }
  3866.         INHERITED Fields(DoToField);
  3867.     END;
  3868.  
  3869. {-------------------------------------------------------------------------------------------}
  3870. {-------------------------------TFastFracAppEngine Methods----------------------------------}
  3871. {-------------------------------------------------------------------------------------------}
  3872. {$S AOpen}
  3873.  
  3874. PROCEDURE TFastFracAppEngine.IFastFracAppEngine(itsDocument: TFracAppDocument);
  3875.  
  3876. { Initializer for TFastFracAppEngine. This makes sure that the version number in the
  3877.   FracHeader of our document is set to the right value, and it creates a TRectStack to hold
  3878.   the subdivided rectangles. After that, it seeds the RectStack with 4 rectangles that cover
  3879.   the entire document. }
  3880.  
  3881.     CONST
  3882.         kInitialNumberOfRects = 40;
  3883.  
  3884.     VAR
  3885.         aRectStack:         TRectStack;
  3886.         tempRect:            Rect;
  3887.  
  3888.     BEGIN
  3889.         fRectStack := NIL;
  3890.         SELF.IFracAppEngine(itsDocument);
  3891.         fFracAppDocument.SetVersion(kFastVersion);    { ensure that we have a vers 2 document }
  3892.  
  3893.         New(aRectStack);
  3894.         FailNil(aRectStack);
  3895.         aRectStack.IRectStack(kInitialNumberOfRects);
  3896.         fRectStack := aRectStack;
  3897.  
  3898.         tempRect := fFracHeaderCopy.calcRect;
  3899.         SELF.DivideAndConquer(tempRect);
  3900.  
  3901.     END;
  3902.  
  3903. {-------------------------------------------------------------------------------------------}
  3904. {$S AClose}
  3905.  
  3906. PROCEDURE TFastFracAppEngine.Free; OVERRIDE;
  3907.  
  3908.     BEGIN
  3909.         FreeIfObject(fRectStack);
  3910.         INHERITED Free;
  3911.     END;
  3912.  
  3913. {-------------------------------------------------------------------------------------------}
  3914. {$S ARes}
  3915.  
  3916. FUNCTION TFastFracAppEngine.CalcCity: Boolean; OVERRIDE;
  3917.  
  3918. { The procedure to do the idle time processing in the document. For a TFastFracAppDocument,
  3919.   we use the Mariani/Silver algorithm. This method assumes that if we are given a bounded
  3920.   region and the boundary of that region is all one color, then the interior of that region
  3921.   is the same color. We can take advantage of this by recursively quartering our pixmap
  3922.   until we start getting rectangles that have solid boundaries. Once we get one, we fill it
  3923.   in all at once with a big PaintRect. To take into account that we don’t live in a perfect
  3924.   world (well...*I* don’t, anyway), and that things start to degrade once we get down to
  3925.   smaller scales, we forego the big PaintRect thing once either side of the rectangle we are
  3926.   looking at gets to be 4 pixels or smaller. This should take care of any skinny color
  3927.   snakes that sneak their way in just because they are too skinny to show up at 72dpi. }
  3928.  
  3929.     VAR
  3930.         thisRect:            Rect;
  3931.         itsColor:            RGBColor;
  3932.         fractalIsDone:        Boolean;
  3933.  
  3934.     PROCEDURE CalcAndGetCPixel(curCol, curRow: Integer; VAR itsRGB: RGBColor);
  3935.  
  3936.     { Modified version of PixWhap. This routine first sees if we calculated the
  3937.       value for the specified pixel already. If so, we just return its color. If
  3938.       not, we call PixWhap to calculate and plot it, and then return that color to
  3939.       the caller. }
  3940.  
  3941.         VAR
  3942.             itsRect:            Rect;
  3943.  
  3944.         BEGIN
  3945.             FAGetCPixel(curCol, curRow, itsRGB);
  3946.             IF EqualRGB(itsRGB, gRGBWhite) THEN BEGIN
  3947.                 SELF.PixWhap(curCol, curRow, itsRect, itsRGB);
  3948.             END;
  3949.  
  3950.         END;
  3951.  
  3952.     FUNCTION BordersAreTheSame(r: Rect; VAR baseRGB: RGBColor): Boolean;
  3953.  
  3954.     { Takes the given rect, and proceeds to calculate and compare the border
  3955.       colors. If they are all the same, we return TRUE. However, as soon as we
  3956.       find a different color, we stop calculating, and return FALSE. }
  3957.  
  3958.         VAR
  3959.             anRGB:                RGBColor;
  3960.             x, y:                Integer;
  3961.             mismatch:            Boolean;
  3962.  
  3963.         PROCEDURE ComeAcross(y: Integer);
  3964.  
  3965.             BEGIN
  3966.                 x := r.left;
  3967.                 REPEAT
  3968.                     x := x + 1;
  3969.                     CalcAndGetCPixel(x, y, anRGB);
  3970.                     mismatch := NOT EqualRGB(anRGB, baseRGB);
  3971.                 UNTIL mismatch OR (x = r.right - 1);
  3972.             END;
  3973.  
  3974.         PROCEDURE ComeDown(x: Integer);
  3975.  
  3976.             BEGIN
  3977.                 y := r.top;
  3978.                 REPEAT
  3979.                     y := y + 1;
  3980.                     CalcAndGetCPixel(x, y, anRGB);
  3981.                     mismatch := NOT EqualRGB(anRGB, baseRGB);
  3982.                 UNTIL mismatch OR (y = r.bottom - 1);
  3983.             END;
  3984.  
  3985.         BEGIN
  3986.             BordersAreTheSame := FALSE;
  3987.  
  3988.             x := r.left;
  3989.             y := r.top;
  3990.             CalcAndGetCPixel(x, y, baseRGB);
  3991.  
  3992.             ComeAcross(r.top);
  3993.             IF mismatch THEN
  3994.                 EXIT(BordersAreTheSame);
  3995.  
  3996.             ComeAcross(r.bottom - 1);
  3997.             IF mismatch THEN
  3998.                 EXIT(BordersAreTheSame);
  3999.  
  4000.             ComeDown(r.left);
  4001.             IF mismatch THEN
  4002.                 EXIT(BordersAreTheSame);
  4003.  
  4004.             ComeDown(r.right - 1);
  4005.             IF mismatch THEN
  4006.                 EXIT(BordersAreTheSame);
  4007.  
  4008.             BordersAreTheSame := TRUE;
  4009.         END;
  4010.  
  4011.     PROCEDURE FillMeUp(thisRect: Rect);
  4012.  
  4013.     { Sort of a small version of the TNormalFracAppEngine. This routine gets called
  4014.       when our rectangle has gotten small enough. It just loops through and
  4015.       calculates the pixels one by one. The rectangle shouldn’t be larger than
  4016.       about 20 or 25 pixels, so this doesn’t take very long. }
  4017.  
  4018.         VAR
  4019.             x, y:                Integer;
  4020.             dummyRect:            Rect;
  4021.             dummyRGB:            RGBColor;
  4022.  
  4023.         BEGIN
  4024.             WITH thisRect DO BEGIN
  4025.                 FOR x := left TO right - 1 DO BEGIN
  4026.                     FOR y := top TO bottom - 1 DO BEGIN
  4027.                         CalcAndGetCPixel(x, y, dummyRGB);
  4028.                     END;                            { FOR y }
  4029.                 END;                                { FOR x }
  4030.             END;                                    { WITH thisRect }
  4031.         END;                                        { PROCEDURE FillMeUp }
  4032.  
  4033.  
  4034.     BEGIN                                            { TFrastFracAppEngine.CalcCity }
  4035.  
  4036.     { Get the next rectangle off of the stack, and start to analyze it. It should
  4037.       fall into one of three states. It could be very small (less than 4 pixels on
  4038.       a side), in which case we just go ahead and fill it in. It could be that
  4039.       color of the entire border is the same, in which case we call PaintRect to
  4040.       fill it in. Finally, we have the rectangle whose border colors are not the
  4041.       same. In this case, we call DivideAndConquer to divvy it up into four smaller
  4042.       rectangles, and push them on the stack for later. }
  4043.  
  4044.         fRectStack.PopRect(thisRect);
  4045.  
  4046.         WITH thisRect DO BEGIN
  4047.             IF ((right - left) <= kMinCCRectSize) | ((bottom - top) <=
  4048.                 kMinCCRectSize) THEN BEGIN
  4049.                 FillMeUp(thisRect);
  4050.                 SELF.ReportRectCompleted(thisRect);
  4051.             END
  4052.             ELSE IF BordersAreTheSame(thisRect, itsColor) THEN BEGIN
  4053.  
  4054.                 SELF.ReportRectCompleted(thisRect);
  4055.  
  4056.             { Do an InsetRect by hand for speed. }
  4057.  
  4058.                 top := top + 1;
  4059.                 left := left + 1;
  4060.                 bottom := bottom - 1;
  4061.                 right := right - 1;
  4062.  
  4063.                 RGBForeColor(itsColor);
  4064.                 PaintRect(thisRect);
  4065.  
  4066.             END
  4067.             ELSE BEGIN
  4068.  
  4069.                 SELF.DivideAndConquer(thisRect);
  4070.  
  4071.             END;
  4072.         END;
  4073.  
  4074.     { Now we have changed another part of the document.  We need to mark it as
  4075.       changed so we can save the document. }
  4076.  
  4077.         fFracAppDocument.BumpChangeCount;
  4078.  
  4079.     { See if we have finished with the document. This is done by seeing if there
  4080.       are any more rectangles to process on the stack. If not, we tell the
  4081.       document that we are all done. We also remove the TRectStack object from
  4082.       memory, and set its reference to NIL so we don’t try to free it again later. }
  4083.  
  4084.         fractalIsDone := fRectStack.IsEmpty;
  4085.         CalcCity := fractalIsDone;
  4086.         IF fractalIsDone THEN BEGIN
  4087.             FreeIfObject(fRectStack);
  4088.             fRectStack := NIL;
  4089.         END;
  4090.  
  4091.     END;                                            { TFastFracAppEngine.CalcCity }
  4092.  
  4093. {-------------------------------------------------------------------------------------------}
  4094. {$S ARes}
  4095.  
  4096. PROCEDURE TFastFracAppEngine.DivideAndConquer(r: Rect);
  4097.  
  4098. { Takes the proferred rectangle, divides it into four parts, and pushes them onto the
  4099.   TRectStack. This is done carefully so as to make sure that the rectangles don’t just abut
  4100.   each other, but that they actually overlap. This is so we don’t go an calculate the right
  4101.   border of one rectangle, and then the left border of the rectangle just to its right. If
  4102.   they overlap, we only calculate one line of pixels, which doubles as the border of
  4103.   two rectangles. }
  4104.  
  4105.     VAR
  4106.         quarterRect:        Rect;
  4107.  
  4108.     BEGIN
  4109.         WITH r DO BEGIN
  4110.  
  4111.         { Push on the lower right corner }
  4112.  
  4113.             quarterRect.top := (bottom + top - 1) DIV 2;
  4114.             quarterRect.left := (right + left - 1) DIV 2;
  4115.             quarterRect.bottom := bottom;
  4116.             quarterRect.right := right;
  4117.             fRectStack.PushRect(quarterRect);
  4118.  
  4119.         { Push on the lower left corner }
  4120.  
  4121.             quarterRect.right := quarterRect.left + 1;
  4122.             quarterRect.left := left;
  4123.             fRectStack.PushRect(quarterRect);
  4124.  
  4125.         { Push on the upper right corner }
  4126.  
  4127.             quarterRect.bottom := quarterRect.top + 1;
  4128.             quarterRect.top := top;
  4129.             quarterRect.left := (right + left - 1) DIV 2;
  4130.             quarterRect.right := right;
  4131.             fRectStack.PushRect(quarterRect);
  4132.  
  4133.         { Push on the upper left corner }
  4134.  
  4135.             quarterRect.right := quarterRect.left + 1;
  4136.             quarterRect.left := left;
  4137.             fRectStack.PushRect(quarterRect);
  4138.  
  4139.         END;
  4140.     END;
  4141.  
  4142. {-------------------------------------------------------------------------------------------}
  4143. {$S AReadFile}
  4144.  
  4145. PROCEDURE TFastFracAppEngine.DoRead(aRefNum: Integer; rsrcExists,
  4146.     forPrinting: Boolean); OVERRIDE;
  4147.  
  4148. { Routine to read the data from the data fork of the file into our engine. This consists of
  4149.   all the Rects that we had in the TRectStack. First, tell the TRectStack to clear itself
  4150.   out. Then, read in a count of the number of rectangles that we wrote out. Next, loop for
  4151.   that number of times, reading a rectangle and pushing it onto the stack. This method is
  4152.   called by our TDocument.DoRead. The file mark is already set, so we only have to read
  4153.   what’s there. }
  4154.  
  4155.     VAR
  4156.         i:                    Integer;
  4157.         recsize:            LONGINT;
  4158.         numberOfRects:        Integer;
  4159.         theRect:            Rect;
  4160.  
  4161.     BEGIN
  4162.         fRectStack.ClearStack;
  4163.         recsize := SizeOf(numberOfRects);
  4164.         FailOSErr(FSRead(aRefNum, recsize, @numberOfRects));
  4165.         IF numberOfRects > 0 THEN BEGIN
  4166.             recsize := SizeOf(theRect);
  4167.             FOR i := 1 TO numberOfRects DO BEGIN
  4168.                 FailOSErr(FSRead(aRefNum, recsize, @theRect));
  4169.                 fRectStack.PushRect(theRect);
  4170.             END;
  4171.         END
  4172.         ELSE BEGIN
  4173.             FreeIfObject(fRectStack);
  4174.             fRectStack := NIL;
  4175.         END;
  4176.     END;
  4177.  
  4178. {-------------------------------------------------------------------------------------------}
  4179. {$S AWriteFile}
  4180.  
  4181. PROCEDURE TFastFracAppEngine.DoWrite(aRefNum: Integer; makingCopy: Boolean); OVERRIDE;
  4182.  
  4183. { Write out our state variables to disk. This consists of all the rectangles we may have on
  4184.   the TRectStack, in addition to a count of the number of rectangles there actually are (for
  4185.   when we need to read them back in). To do this, we call a TRectStack method that performs
  4186.   a routine for each item on the stack. The routine that we tell it to perform is one that
  4187.   takes the current rectangle, and writes it to disk.. This method is called by
  4188.   TFracAppDocument.DoWrite, and it sets up the file mark for us, so we just need to start
  4189.   writing to wherever we happen to be. }
  4190.  
  4191.     VAR
  4192.         recsize:            LONGINT;
  4193.         numberOfRects:        Integer;
  4194.  
  4195.     PROCEDURE WriteRect(theRect: Rect);
  4196.  
  4197.         BEGIN
  4198.             FailOSErr(FSWrite(aRefNum, recsize, @theRect));
  4199.         END;
  4200.  
  4201.     BEGIN
  4202.  
  4203.         recsize := SizeOf(numberOfRects);
  4204.         IF fRectStack = NIL THEN BEGIN
  4205.             numberOfRects := 0;
  4206.             FailOSErr(FSWrite(aRefNum, recsize, @numberOfRects));
  4207.         END
  4208.         ELSE BEGIN
  4209.             numberOfRects := fRectStack.GetSize;
  4210.             FailOSErr(FSWrite(aRefNum, recsize, @numberOfRects));
  4211.  
  4212.             recsize := SizeOf(Rect);
  4213.             fRectStack.EachRect(WriteRect, kIterateForward);
  4214.         END;
  4215.  
  4216.     END;
  4217.  
  4218. {-------------------------------------------------------------------------------------------}
  4219. {$S ARes}
  4220.  
  4221. PROCEDURE TFastFracAppEngine.ReportRectCompleted(dirtyRect: Rect); OVERRIDE;
  4222.  
  4223. { Bottleneck used by CalcCity to inform the document that part of the fractal has been
  4224.   freshly calculated and needs to be copied to the screen. We override it here to fix up the
  4225.   rectangle. Our rectangles are chosen so that there is a little bit of overlap between them
  4226.   and the rects next to them. We have to get rid of that overlap so that updating is more
  4227.   efficient, and so that our “areaCompleted” running total is accurate. }
  4228.  
  4229.     BEGIN
  4230.         WITH dirtyRect DO BEGIN
  4231.             IF top > 0 THEN
  4232.                 top := top + 1;
  4233.             IF left > 0 THEN
  4234.                 left := left + 1;
  4235.         END;
  4236.         INHERITED ReportRectCompleted(dirtyRect);
  4237.     END;
  4238.  
  4239. {-------------------------------------------------------------------------------------------}
  4240. {$S AFields}
  4241.  
  4242. PROCEDURE TFastFracAppEngine.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
  4243.                                                         fieldType: Integer)); OVERRIDE;
  4244.  
  4245.     BEGIN
  4246.         DoToField('TFastFracAppEngine', NIL, bClass);
  4247.         DoToField('fRectStack', @fRectStack, bObject);
  4248.  
  4249.         { Print fields of anscestors }
  4250.         INHERITED Fields(DoToField);
  4251.     END;
  4252.